Coverage for cc_modules/cc_constants.py: 97%
662 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
1"""
2camcops_server/cc_modules/cc_constants.py
4===============================================================================
6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CamCOPS.
11 CamCOPS 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 CamCOPS 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 CamCOPS. If not, see <https://www.gnu.org/licenses/>.
24===============================================================================
26**Various constants.**
28"""
30# Helpful UTF-8 characters: ‘’ “” – — × • ≤ ≥ ≠ ± →
32import logging
33import multiprocessing
34import os
35from typing import cast
37from cardinal_pythonlib.randomness import create_base64encoded_randomness
38from cardinal_pythonlib.sqlalchemy.session import make_mysql_url
39from cardinal_pythonlib.tcpipconst import Ports
40from cardinal_pythonlib.uriconst import UriSchemes
42from camcops_server.cc_modules.cc_baseconstants import (
43 DEFAULT_EXTRA_STRINGS_DIR,
44 ENVVAR_GENERATING_CAMCOPS_DOCS,
45 LINUX_DEFAULT_LOCK_DIR,
46 LINUX_DEFAULT_USER_DOWNLOAD_DIR,
47 STATIC_ROOT_DIR,
48)
49from camcops_server.cc_modules.cc_language import DEFAULT_LOCALE
52# =============================================================================
53# Number of ID numbers. Don't alter this lightly; influences database fields.
54# =============================================================================
56NUMBER_OF_IDNUMS_DEFUNCT = 8 # DEFUNCT BUT DO NOT REMOVE OR ALTER. EIGHT.
57# ... In older versions: determined number of ID number fields.
58# (Now this is arbitrary.) Still used to support old clients.
61# =============================================================================
62# File types
63# =============================================================================
66class FileType(object):
67 """
68 Used to represent output formats and their file extensions.
69 """
71 HTML = "html"
72 PDF = "pdf"
73 XML = "xml"
76# =============================================================================
77# Encodings
78# =============================================================================
80ASCII = "ascii"
81UTF8 = "utf8"
84# =============================================================================
85# Launching
86# =============================================================================
88DEFAULT_FLOWER_ADDRESS = "127.0.0.1"
89DEFAULT_FLOWER_PORT = (
90 5555 # http://docs.celeryproject.org/en/latest/userguide/monitoring.html
91)
94# =============================================================================
95# Webview constants
96# =============================================================================
98DEFAULT_ROWS_PER_PAGE = 25
99DEVICE_NAME_FOR_SERVER = "server" # Do not alter.
100USER_NAME_FOR_SYSTEM = "system" # Do not alter.
102# Address to download Windows/Mac versions:
103GITHUB_RELEASES_URL = (
104 "https://github.com/ucam-department-of-psychiatry/camcops/releases/"
105)
107MINIMUM_PASSWORD_LENGTH = 10
109OBSCURE_PHONE_ASTERISKS = "*" * 10
110OBSCURE_EMAIL_ASTERISKS = "*" * 5
113# =============================================================================
114# Other display constants
115# =============================================================================
117JSON_INDENT = 4
120# =============================================================================
121# Date formats
122# =============================================================================
125class DateFormat(object):
126 """
127 Assorted date/time formats.
128 """
130 SHORT_DATE = "%d %b %Y" # e.g. 24 Jul 2013
131 LONG_DATE = "%d %B %Y" # e.g. 24 July 2013
132 LONG_DATE_WITH_DAY = "%a %d %B %Y" # e.g. Wed 24 July 2013
133 LONG_DATETIME = "%d %B %Y, %H:%M %z" # e.g. 24 July 2013, 20:04 +0100
134 LONG_DATETIME_WITH_DAY = (
135 "%a %d %B %Y, %H:%M %z" # e.g. Wed 24 July 2013, 20:04 +0100
136 )
137 LONG_DATETIME_WITH_DAY_NO_TZ = (
138 "%a %d %B %Y, %H:%M" # e.g. Wed 24 July 2013, 20:04
139 )
140 SHORT_DATETIME_WITH_DAY_NO_TZ = (
141 "%a %d %b %Y, %H:%M" # e.g. Wed 24 Jul 2013, 20:04
142 )
143 LONG_DATETIME_SECONDS = "%d %B %Y, %H:%M:%S %z"
144 SHORT_DATETIME = "%d %b %Y, %H:%M %z" # e.g. 24 Jul 2013, 20:04 +0100
145 SHORT_DATETIME_NO_TZ = "%d %b %Y, %H:%M" # e.g. 24 Jul 2013, 20:04
146 SHORT_DATETIME_SECONDS = (
147 "%d %b %Y, %H:%M:%S %z" # e.g. 24 Jul 2013, 20:04:23 +0100
148 )
149 HOURS_MINUTES = "%H:%M" # e.g. 20:04
150 ISO8601 = "%Y-%m-%dT%H:%M:%S%z" # e.g. 2013-07-24T20:04:07+0100
151 ISO8601_HUMANIZED_TO_MINUTES = "%Y-%m-%d %H:%M" # e.g. 2013-07-24 20:04
152 ISO8601_HUMANIZED_TO_SECONDS = (
153 "%Y-%m-%d %H:%M:%S" # e.g. 2013-07-24 20:04:23
154 )
155 ISO8601_HUMANIZED_TO_SECONDS_TZ = (
156 "%Y-%m-%d %H:%M:%S %z" # e.g. 2013-07-24 20:04:23 +0100
157 )
158 ISO8601_DATE_ONLY = "%Y-%m-%d" # e.g. 2013-07-24
159 FHIR_DATE = "%Y-%m-%d" # e.g. 2013-07-24
160 # FHIR_DATETIME -- use x.isoformat()
161 FHIR_TIME = "%H:%M:%S"
162 # ... https://www.hl7.org/fhir/codesystem-item-type.html#item-type-time
163 FILENAME = "%Y-%m-%dT%H%M%S" # e.g. 2013-07-24T200459
164 FILENAME_DATE_ONLY = "%Y-%m-%d" # e.g. 20130724
165 HL7_DATETIME = "%Y%m%d%H%M%S%z" # e.g. 20130724200407+0100
166 HL7_DATE = "%Y%m%d" # e.g. 20130724
167 ERA = "%Y-%m-%dT%H:%M:%SZ" # e.g. 2013-07-24T20:03:07Z
168 # http://www.hl7standards.com/blog/2008/07/25/hl7-time-zone-qualification/
169 RIO_EXPORT_UK = "%d/%m/%Y %H:%M" # e.g. 01/12/2014 09:45
172# =============================================================================
173# FHIR constants
174# =============================================================================
176CAMCOPS_DEFAULT_FHIR_APP_ID = "camcops"
179# =============================================================================
180# Permitted values in fields: some common settings
181# =============================================================================
184class PV(object):
185 """
186 Collections of permitted values.
187 """
189 BIT = (0, 1)
191 # Red/Yellow/Green
192 RYG = ("R", "Y", "G")
195NO_CHAR = "N"
196YES_CHAR = "Y"
198# Database values:
199SEX_FEMALE = "F"
200SEX_MALE = "M"
201SEX_OTHER_UNSPECIFIED = "X"
202POSSIBLE_SEX_VALUES = (SEX_FEMALE, SEX_MALE, SEX_OTHER_UNSPECIFIED)
205# =============================================================================
206# Field names/specifications
207# =============================================================================
209# Do not alter these!
210TABLET_ID_FIELD = "id"
211MOVE_OFF_TABLET_FIELD = "_move_off_tablet"
212CLIENT_DATE_FIELD = "when_last_modified"
214# Used for old client support, and TSV field names etc.:
215FP_ID_NUM = "idnum"
216FP_ID_DESC = "iddesc"
217FP_ID_SHORT_DESC = "idshortdesc"
219# Additional fields for some exports:
220EXTRA_IDNUM_FIELD_PREFIX = "_patient_idnum"
221EXTRA_TASK_TABLENAME_FIELD = "_task_tablename"
222EXTRA_TASK_SERVER_PK_FIELD = "_task_pk"
223EXTRA_COMMENT_PREFIX = "(EXTRA) "
226# =============================================================================
227# Other special values
228# =============================================================================
230CAMCOPS_URL = "https://camcops.readthedocs.io/"
231ERA_NOW = "NOW" # defines the current era in database records
234# =============================================================================
235# PDF engine: now always "pdfkit".
236# =============================================================================
238# PDF_ENGINE = "xhtml2pdf" # working
239PDF_ENGINE = "pdfkit" # working
240# PDF_ENGINE = "weasyprint" # working but table <tr> element bugs
241# ... value must be one of: xhtml2pdf, weasyprint, pdfkit
244# =============================================================================
245# Simple constants for HTML/plots/display
246# =============================================================================
249class PlotDefaults(object):
250 """
251 Defaults used with matplotlib plotting.
252 """
254 DEFAULT_PLOT_DPI = 300
256 FULLWIDTH_PLOT_WIDTH = 6.7 # inches: full width is ~170mm
258 # zorder parameter:
259 # - higher = on top
260 # - defaults relate to the type of thing being plotted:
261 # https://matplotlib.org/3.1.1/gallery/misc/zorder_demo.html
262 # - Patch / PatchCollection = 1
263 # - Line2D / LineCollection = 2
264 # - Text = 3
265 # - within a Line2D object (points and lines), the default is
266 # "markers on top of lines"
267 ZORDER_PRESET_LINES = 1
268 ZORDER_PRESET_LABELS = 2
269 ZORDER_DATA_LINES_POINTS = 3 # the default
272class MatplotlibConstants(object):
273 """
274 Constants used by matplotlib
275 """
277 # https://matplotlib.org/tutorials/colors/colors.html
278 COLOUR_BLACK = "k"
279 COLOUR_BLUE = "b"
280 COLOUR_GREEN = "g"
281 COLOUR_GREY_50 = "0.5"
282 COLOUR_GREY_90 = "0.9" # 0.9 is close to white (0 black, 1 white)
283 COLOUR_RED = "r"
285 # https://matplotlib.org/gallery/lines_bars_and_markers/line_styles_reference.html # noqa
286 # https://matplotlib.org/3.1.0/gallery/lines_bars_and_markers/linestyles.html # noqa
287 LINESTYLE_DOTTED = ":"
288 LINESTYLE_SOLID = "-"
289 LINESTYLE_NONE = "None"
291 # https://matplotlib.org/3.1.1/api/markers_api.html
292 MARKER_CIRCLE = "o"
293 MARKER_NONE = "" # also "None", " "
294 MARKER_PLUS = "+"
295 MARKER_STAR = "*"
297 WHOLE_PANEL = 111 # as in: ax = fig.add_subplot(111)
300# Debugging option
301USE_SVG_IN_HTML = True # set to False for PNG debugging
304# =============================================================================
305# CSS/HTML constants
306# =============================================================================
308CSS_PAGED_MEDIA = PDF_ENGINE != "pdfkit"
310WKHTMLTOPDF_OPTIONS = { # dict for pdfkit
311 "page-size": "A4",
312 "margin-left": "20mm",
313 "margin-right": "20mm",
314 "margin-top": "21mm", # from paper edge down to top of content?
315 # ... inaccurate
316 "margin-bottom": "24mm", # from paper edge up to bottom of content?
317 # ... inaccurate
318 "header-spacing": "3", # mm, from content up to bottom of header
319 "footer-spacing": "3", # mm, from content down to top of footer
320 "quiet": "", # Suppress "Loading pages (1/6)" etc.
321 "enable-local-file-access": "",
322}
325class CssClass(object):
326 """
327 CSS names.
329 Values should match e.g. ``camcops_server/templates/css/css_base.mako``.
330 """
332 BAD_ID_POLICY_MILD = "badidpolicy_mild"
333 BAD_ID_POLICY_SEVERE = "badidpolicy_severe"
334 BANNER = "banner"
335 BANNER_REFERRAL_GENERAL_ADULT = "banner_referral_general_adult"
336 BANNER_REFERRAL_OLD_AGE = "banner_referral_old_age"
337 BANNER_REFERRAL_SUBSTANCE_MISUSE = "banner_referral_substance_misuse"
338 CENTREGAP_TD = "centregap_td"
339 CLINICIAN = "clinician"
340 COPYRIGHT = "copyright"
341 CTV_DATELIMIT_START = "ctv_datelimit_start"
342 CTV_DATELIMIT_END = "ctv_datelimit_end"
343 CTV_TASKHEADING = "ctv_taskheading"
344 CTV_FIELDHEADING = "ctv_fieldheading"
345 CTV_FIELDSUBHEADING = "ctv_fieldsubheading"
346 CTV_FIELDDESCRIPTION = "ctv_fielddescription"
347 CTV_FIELDCONTENT = "ctv_fieldcontent"
348 CTV_WARNINGS = "ctv_warnings"
349 ERROR = "error"
350 EXPLANATION = "explanation"
351 EXTRADETAIL = "extradetail"
352 EXTRADETAIL2 = "extradetail2"
353 FILTER = "filter"
354 FILTERS = "filters"
355 FIGURE = "figure"
356 FOOTNOTES = "footnotes"
357 FORMTITLE = "formtitle"
358 GENERAL = "general"
359 GREEN = "green"
360 HANGINGINDENT = "hangingindent"
361 HEADING = "heading"
362 HIGHLIGHT = "highlight"
363 IMAGE_TD = "image_td"
364 IMPORTANT = "important"
365 INCOMPLETE = "incomplete"
366 INDENT = "indent"
367 INDENTED = "indented"
368 LIVE_ON_TABLET = "live_on_tablet"
369 LOGO_LEFT = "logo_left"
370 LOGO_RIGHT = "logo_right"
371 NAVIGATION = "navigation"
372 NOBORDER = "noborder"
373 NOBORDERPHOTO = "noborderphoto"
374 OFFICE = "office"
375 PATIENT = "patient"
376 PHOTO = "photo"
377 PDF_LOGO_HEADER = "pdf_logo_header"
378 QA_TABLE_HEADING = "qa_tableheading"
379 RESPONDENT = "respondent"
380 SIGNATURE = "signature"
381 SIGNATURE_LABEL = "signature_label"
382 SMALLPRINT = "smallprint"
383 SPECIALNOTE = "specialnote"
384 SUBHEADING = "subheading"
385 SUBSUBHEADING = "subsubheading"
386 SUMMARY = "summary"
387 SUPERUSER = "superuser"
388 TASKCONFIG = "taskconfig"
389 TASKDETAIL = "taskdetail"
390 TASKHEADER = "taskheader"
391 TRACKERHEADER = "trackerheader"
392 TRACKER_ALL_CONSISTENT = "tracker_all_consistent"
393 WARNING = "warning"
394 WEB_LOGO_HEADER = "web_logo_header"
397# =============================================================================
398# Task constants
399# =============================================================================
401ANON_PATIENT = "XXXX"
402DATA_COLLECTION_ONLY_DIV = """
403 <div class="copyright">
404 Reproduction of the original task/scale is not permitted.
405 This is a data collection tool only; use it only in conjunction with
406 a licensed copy of the original task.
407 </div>
408"""
409DATA_COLLECTION_UNLESS_UPGRADED_DIV = """
410 <div class="copyright">
411 Reproduction of the original task/scale is not permitted as part of
412 CamCOPS. This is a data collection tool only, unless the hosting
413 institution has supplied task text via its own permissions. <b>Any such
414 text, if shown here, is not part of CamCOPS, and copyright in
415 it belongs to the original task’s copyright holder.</b> Use this data
416 collection tool only in conjunction with a licensed copy of the
417 original task.
418 </div>
419"""
420ICD10_COPYRIGHT_DIV = """
421 <div class="copyright">
422 ICD-10 criteria: Copyright © 1992 World Health Organization.
423 Used here with permission.
424 </div>
425"""
426INVALID_VALUE = "[invalid_value]"
428QUESTION = "Question"
430SPREADSHEET_PATIENT_FIELD_PREFIX = "_patient_"
433# =============================================================================
434# Config constants
435# =============================================================================
437CONFIG_FILE_SITE_SECTION = "site"
438CONFIG_FILE_SERVER_SECTION = "server"
439CONFIG_FILE_EXPORT_SECTION = "export"
440CONFIG_FILE_SMS_BACKEND_PREFIX = "sms_backend"
443class ConfigParamSite(object):
444 """
445 Parameters allowed in the main ``[site]`` section of the CamCOPS config
446 file.
447 """
449 ALLOW_INSECURE_COOKIES = "ALLOW_INSECURE_COOKIES"
450 CAMCOPS_LOGO_FILE_ABSOLUTE = "CAMCOPS_LOGO_FILE_ABSOLUTE"
451 CLIENT_API_LOGLEVEL = "CLIENT_API_LOGLEVEL"
452 CTV_FILENAME_SPEC = "CTV_FILENAME_SPEC"
453 DB_URL = "DB_URL"
454 DB_ECHO = "DB_ECHO"
455 DISABLE_PASSWORD_AUTOCOMPLETE = "DISABLE_PASSWORD_AUTOCOMPLETE"
456 EMAIL_FROM = "EMAIL_FROM"
457 EMAIL_HOST = "EMAIL_HOST"
458 EMAIL_HOST_PASSWORD = "EMAIL_HOST_PASSWORD"
459 EMAIL_HOST_PASSWORD_GNU_PASS_LOOKUP = "EMAIL_HOST_PASSWORD_GNU_PASS_LOOKUP"
460 EMAIL_HOST_USERNAME = "EMAIL_HOST_USERNAME"
461 EMAIL_PORT = "EMAIL_PORT"
462 EMAIL_REPLY_TO = "EMAIL_REPLY_TO"
463 EMAIL_SENDER = "EMAIL_SENDER"
464 EMAIL_USE_TLS = "EMAIL_USE_TLS"
465 EXTRA_STRING_FILES = "EXTRA_STRING_FILES"
466 LANGUAGE = "LANGUAGE"
467 LOCAL_INSTITUTION_URL = "LOCAL_INSTITUTION_URL"
468 LOCAL_LOGO_FILE_ABSOLUTE = "LOCAL_LOGO_FILE_ABSOLUTE"
469 LOCKOUT_DURATION_INCREMENT_MINUTES = "LOCKOUT_DURATION_INCREMENT_MINUTES"
470 LOCKOUT_THRESHOLD = "LOCKOUT_THRESHOLD"
471 MFA_METHODS = "MFA_METHODS"
472 MFA_TIMEOUT_S = "MFA_TIMEOUT_S"
473 PASSWORD_CHANGE_FREQUENCY_DAYS = "PASSWORD_CHANGE_FREQUENCY_DAYS"
474 PATIENT_SPEC = "PATIENT_SPEC"
475 PATIENT_SPEC_IF_ANONYMOUS = "PATIENT_SPEC_IF_ANONYMOUS"
476 PERMIT_IMMEDIATE_DOWNLOADS = "PERMIT_IMMEDIATE_DOWNLOADS"
477 REGION_CODE = "REGION_CODE"
478 RESTRICTED_TASKS = "RESTRICTED_TASKS"
479 SESSION_COOKIE_SECRET = "SESSION_COOKIE_SECRET"
480 SESSION_TIMEOUT_MINUTES = "SESSION_TIMEOUT_MINUTES"
481 SESSION_CHECK_USER_IP = "SESSION_CHECK_USER_IP"
482 SMS_BACKEND = "SMS_BACKEND"
483 SNOMED_TASK_XML_FILENAME = "SNOMED_TASK_XML_FILENAME"
484 SNOMED_ICD9_XML_FILENAME = "SNOMED_ICD9_XML_FILENAME"
485 SNOMED_ICD10_XML_FILENAME = "SNOMED_ICD10_XML_FILENAME"
486 TASK_FILENAME_SPEC = "TASK_FILENAME_SPEC"
487 TRACKER_FILENAME_SPEC = "TRACKER_FILENAME_SPEC"
488 USER_DOWNLOAD_DIR = "USER_DOWNLOAD_DIR"
489 USER_DOWNLOAD_FILE_LIFETIME_MIN = "USER_DOWNLOAD_FILE_LIFETIME_MIN"
490 USER_DOWNLOAD_MAX_SPACE_MB = "USER_DOWNLOAD_MAX_SPACE_MB"
491 WEBVIEW_LOGLEVEL = "WEBVIEW_LOGLEVEL"
492 WKHTMLTOPDF_FILENAME = "WKHTMLTOPDF_FILENAME"
495class ConfigParamServer(object):
496 """
497 Parameters allowed in the web server (``[server]``) section of the CamCOPS
498 config file.
499 """
501 CHERRYPY_LOG_SCREEN = "CHERRYPY_LOG_SCREEN"
502 CHERRYPY_ROOT_PATH = "CHERRYPY_ROOT_PATH"
503 CHERRYPY_SERVER_NAME = "CHERRYPY_SERVER_NAME"
504 CHERRYPY_THREADS_MAX = "CHERRYPY_THREADS_MAX"
505 CHERRYPY_THREADS_START = "CHERRYPY_THREADS_START"
506 DEBUG_REVERSE_PROXY = "DEBUG_REVERSE_PROXY"
507 DEBUG_SHOW_GUNICORN_OPTIONS = "DEBUG_SHOW_GUNICORN_OPTIONS"
508 DEBUG_TOOLBAR = "DEBUG_TOOLBAR"
509 EXTERNAL_URL_SCHEME = "EXTERNAL_URL_SCHEME"
510 EXTERNAL_SERVER_NAME = "EXTERNAL_SERVER_NAME"
511 EXTERNAL_SERVER_PORT = "EXTERNAL_SERVER_PORT"
512 EXTERNAL_SCRIPT_NAME = "EXTERNAL_SCRIPT_NAME"
513 GUNICORN_DEBUG_RELOAD = "GUNICORN_DEBUG_RELOAD"
514 GUNICORN_NUM_WORKERS = "GUNICORN_NUM_WORKERS"
515 GUNICORN_TIMEOUT_S = "GUNICORN_TIMEOUT_S"
516 HOST = "HOST"
517 PORT = "PORT"
518 PROXY_HTTP_HOST = "PROXY_HTTP_HOST"
519 PROXY_REMOTE_ADDR = "PROXY_REMOTE_ADDR"
520 PROXY_REWRITE_PATH_INFO = "PROXY_REWRITE_PATH_INFO"
521 PROXY_SCRIPT_NAME = "PROXY_SCRIPT_NAME"
522 PROXY_SERVER_NAME = "PROXY_SERVER_NAME"
523 PROXY_SERVER_PORT = "PROXY_SERVER_PORT"
524 PROXY_URL_SCHEME = "PROXY_URL_SCHEME"
525 SHOW_REQUEST_IMMEDIATELY = "SHOW_REQUEST_IMMEDIATELY"
526 SHOW_REQUESTS = "SHOW_REQUESTS"
527 SHOW_RESPONSE = "SHOW_RESPONSE"
528 SHOW_TIMING = "SHOW_TIMING"
529 SSL_CERTIFICATE = "SSL_CERTIFICATE"
530 SSL_PRIVATE_KEY = "SSL_PRIVATE_KEY"
531 STATIC_CACHE_DURATION_S = "STATIC_CACHE_DURATION_S"
532 TRUSTED_PROXY_HEADERS = "TRUSTED_PROXY_HEADERS"
533 UNIX_DOMAIN_SOCKET = "UNIX_DOMAIN_SOCKET"
536class ConfigParamExportGeneral(object):
537 """
538 Parameters allowed in the ``[export]`` section of the CamCOPS config file.
539 """
541 CELERY_BEAT_EXTRA_ARGS = "CELERY_BEAT_EXTRA_ARGS"
542 CELERY_BEAT_SCHEDULE_DATABASE = "CELERY_BEAT_SCHEDULE_DATABASE"
543 CELERY_BROKER_URL = "CELERY_BROKER_URL"
544 CELERY_WORKER_EXTRA_ARGS = "CELERY_WORKER_EXTRA_ARGS"
545 CELERY_EXPORT_TASK_RATE_LIMIT = "CELERY_EXPORT_TASK_RATE_LIMIT"
546 EXPORT_LOCKDIR = "EXPORT_LOCKDIR"
547 RECIPIENTS = "RECIPIENTS"
548 SCHEDULE = "SCHEDULE"
549 SCHEDULE_TIMEZONE = "SCHEDULE_TIMEZONE"
552class ConfigParamExportRecipient(object):
553 """
554 Possible configuration file parameters that relate to "export recipient"
555 definitions.
556 """
558 ALL_GROUPS = "ALL_GROUPS"
559 DB_ADD_SUMMARIES = "DB_ADD_SUMMARIES"
560 DB_ECHO = "DB_ECHO"
561 DB_INCLUDE_BLOBS = "DB_INCLUDE_BLOBS"
562 DB_PATIENT_ID_PER_ROW = "DB_PATIENT_ID_PER_ROW"
563 DB_URL = "DB_URL"
564 EMAIL_BCC = "EMAIL_BCC"
565 EMAIL_BODY = "EMAIL_BODY"
566 EMAIL_BODY_IS_HTML = "EMAIL_BODY_IS_HTML"
567 EMAIL_CC = "EMAIL_CC"
568 EMAIL_KEEP_MESSAGE = "EMAIL_KEEP_MESSAGE"
569 EMAIL_RECIPIENTS = "EMAIL_RECIPIENTS"
570 EMAIL_PATIENT_SPEC = "EMAIL_PATIENT_SPEC"
571 EMAIL_PATIENT_SPEC_IF_ANONYMOUS = "EMAIL_PATIENT_SPEC_IF_ANONYMOUS"
572 EMAIL_SUBJECT = "EMAIL_SUBJECT"
573 EMAIL_TIMEOUT = "EMAIL_TIMEOUT"
574 EMAIL_TO = "EMAIL_TO"
575 END_DATETIME_UTC = "END_DATETIME_UTC"
576 FHIR_API_URL = "FHIR_API_URL"
577 FHIR_APP_ID = "FHIR_APP_ID"
578 FHIR_APP_SECRET = "FHIR_APP_SECRET"
579 FHIR_LAUNCH_TOKEN = "FHIR_LAUNCH_TOKEN"
580 FHIR_CONCURRENT = "FHIR_CONCURRENT"
581 FILE_EXPORT_RIO_METADATA = "FILE_EXPORT_RIO_METADATA"
582 FILE_FILENAME_SPEC = "FILE_FILENAME_SPEC"
583 FILE_MAKE_DIRECTORY = "FILE_MAKE_DIRECTORY"
584 FILE_OVERWRITE_FILES = "FILE_OVERWRITE_FILES"
585 FILE_PATIENT_SPEC = "FILE_PATIENT_SPEC"
586 FILE_PATIENT_SPEC_IF_ANONYMOUS = "FILE_PATIENT_SPEC_IF_ANONYMOUS"
587 FILE_SCRIPT_AFTER_EXPORT = "FILE_SCRIPT_AFTER_EXPORT"
588 FINALIZED_ONLY = "FINALIZED_ONLY"
589 GROUPS = "GROUPS"
590 HL7_DEBUG_DIVERT_TO_FILE = "HL7_DEBUG_DIVERT_TO_FILE"
591 HL7_DEBUG_TREAT_DIVERTED_AS_SENT = "HL7_DEBUG_TREAT_DIVERTED_AS_SENT"
592 HL7_HOST = "HL7_HOST"
593 HL7_KEEP_MESSAGE = "HL7_KEEP_MESSAGE"
594 HL7_KEEP_REPLY = "HL7_KEEP_REPLY"
595 HL7_NETWORK_TIMEOUT_MS = "HL7_NETWORK_TIMEOUT_MS"
596 HL7_PING_FIRST = "HL7_PING_FIRST"
597 HL7_PORT = "HL7_PORT"
598 IDNUM_AA_PREFIX = "IDNUM_AA_" # unusual; prefix not parameter
599 IDNUM_TYPE_PREFIX = "IDNUM_TYPE_" # unusual; prefix not parameter
600 INCLUDE_ANONYMOUS = "INCLUDE_ANONYMOUS"
601 PRIMARY_IDNUM = "PRIMARY_IDNUM"
602 PUSH = "PUSH"
603 REDCAP_API_KEY = "REDCAP_API_KEY"
604 REDCAP_API_URL = "REDCAP_API_URL"
605 REDCAP_FIELDMAP_FILENAME = "REDCAP_FIELDMAP_FILENAME"
606 REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY = (
607 "REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY"
608 )
609 RIO_DOCUMENT_TYPE = "RIO_DOCUMENT_TYPE"
610 RIO_IDNUM = "RIO_IDNUM"
611 RIO_UPLOADING_USER = "RIO_UPLOADING_USER"
612 START_DATETIME_UTC = "START_DATETIME_UTC"
613 TASK_FORMAT = "TASK_FORMAT"
614 TASKS = "TASKS"
615 TRANSMISSION_METHOD = "TRANSMISSION_METHOD"
616 XML_FIELD_COMMENTS = "XML_FIELD_COMMENTS"
619class MfaMethod:
620 """
621 Open multi-factor authentication (MFA) standards are defined in RFC 4226
622 (HOTP: An HMAC-Based One-Time Password Algorithm) and in RFC 6238 (TOTP:
623 Time-Based One-Time Password Algorithm).
625 HMAC: Hash-based Message Authentication Code
626 https://en.wikipedia.org/wiki/HMAC
628 Values must be in lower case.
629 """
631 HOTP_EMAIL = "hotp_email" # Send a code by email
632 HOTP_SMS = "hotp_sms" # Send a code by SMS
633 NO_MFA = "no_mfa" # No multi-factor authentication; username/password only
634 TOTP = "totp" # Use an app such as Google Authenticator, Twilio Authy
636 @classmethod
637 def valid(cls, method: str) -> bool:
638 """
639 Is the method a known MFA method (including "no MFA")?
640 """
641 return method in (cls.HOTP_EMAIL, cls.HOTP_SMS, cls.NO_MFA, cls.TOTP)
643 @classmethod
644 def requires_second_step(cls, method: str) -> bool:
645 """
646 Does the method require a second authentication step?
647 """
648 return method in (cls.HOTP_EMAIL, cls.HOTP_SMS, cls.TOTP)
650 @classmethod
651 def clean(cls, method: str) -> str:
652 """
653 Returns a valid method, even if the input isn't.
654 Defaults to NO_MFA.
655 """
656 if cls.requires_second_step(method):
657 return method
658 else:
659 # e.g. NO_MFA, None, "none", other junk
660 return cls.NO_MFA
663class SmsBackendNames:
664 """
665 Names of allowed SMS backends.
666 """
668 CONSOLE = "console"
669 KAPOW = "kapow"
670 TWILIO = "twilio"
673class DockerConstants(object):
674 """
675 Constants for the Docker environment.
676 """
678 # Directories
679 DOCKER_CAMCOPS_ROOT_DIR = "/camcops"
680 CONFIG_DIR = os.path.join(DOCKER_CAMCOPS_ROOT_DIR, "cfg")
681 TMP_DIR = os.path.join(DOCKER_CAMCOPS_ROOT_DIR, "tmp")
682 VENV_DIR = os.path.join(DOCKER_CAMCOPS_ROOT_DIR, "venv")
684 DEFAULT_USER_DOWNLOAD_DIR = os.path.join(TMP_DIR, "user_downloads")
685 DEFAULT_LOCKDIR = os.path.join(TMP_DIR, "lock")
687 # Container (internal) names
688 CONTAINER_RABBITMQ = "rabbitmq"
689 CONTAINER_MYSQL = "mysql"
691 # Other
692 CELERY_BROKER_URL = f"amqp://{CONTAINER_RABBITMQ}:{Ports.AMQP}/"
693 DEFAULT_MYSQL_CAMCOPS_USER = "camcops"
694 HOST = "0.0.0.0"
695 # ... not "localhost" or "127.0.0.1"; see
696 # https://nickjanetakis.com/blog/docker-tip-54-fixing-connection-reset-by-peer-or-similar-errors # noqa
699# =============================================================================
700# Configuration defaults
701# =============================================================================
704class ConfigDefaults(object):
705 """
706 Contains default values for the config, plus some cosmetic defaults for
707 generating specimen config files.
709 - Re ``CHERRYPY_THREADS_MAX``: beware the default MySQL connection limit of
710 151; https://dev.mysql.com/doc/refman/5.7/en/too-many-connections.html
711 """
713 # [site] section
714 ALLOW_INSECURE_COOKIES = False
715 CAMCOPS_LOGO_FILE_ABSOLUTE = os.path.join(
716 STATIC_ROOT_DIR, "logo_camcops.png"
717 )
718 CLIENT_API_LOGLEVEL = logging.INFO
719 CLIENT_API_LOGLEVEL_TEXTFORMAT = "info" # should match CLIENT_API_LOGLEVEL
720 DB_DATABASE = "camcops" # for demo configs only
721 DB_ECHO = False
722 DB_PORT = str(Ports.MYSQL) # for demo configs only
723 DB_SERVER = "localhost" # for demo configs only
724 DB_USER = "YYY_USERNAME_REPLACE_ME" # cosmetic; for demo configs only
725 DB_PASSWORD = "ZZZ_PASSWORD_REPLACE_ME" # cosmetic; for demo configs only
726 DISABLE_PASSWORD_AUTOCOMPLETE = True
727 EMAIL_PORT = Ports.SMTP_MSA
728 EMAIL_USE_TLS = True
729 EXTERNAL_URL_SCHEME = UriSchemes.HTTPS
730 EXTERNAL_SERVER_NAME = "localhost"
731 EXTRA_STRING_FILES = os.path.join(
732 DEFAULT_EXTRA_STRINGS_DIR, "*.xml"
733 ) # cosmetic; for demo configs only
734 LANGUAGE = DEFAULT_LOCALE
735 LOCAL_INSTITUTION_URL = "https://camcops.readthedocs.io/"
736 LOCAL_LOGO_FILE_ABSOLUTE = os.path.join(STATIC_ROOT_DIR, "logo_local.png")
737 LOCKOUT_DURATION_INCREMENT_MINUTES = 10
738 LOCKOUT_THRESHOLD = 10
739 MFA_METHODS = [MfaMethod.NO_MFA]
740 MFA_TIMEOUT_S = 600 # zero for never
741 PASSWORD_CHANGE_FREQUENCY_DAYS = 0 # zero for never
742 PATIENT_SPEC_IF_ANONYMOUS = "anonymous"
743 PERMIT_IMMEDIATE_DOWNLOADS = False
744 REGION_CODE = "GB"
745 SESSION_CHECK_USER_IP = True
746 SESSION_TIMEOUT_MINUTES = 30
747 SMS_BACKEND = SmsBackendNames.CONSOLE
748 USER_DOWNLOAD_DIR = (
749 LINUX_DEFAULT_USER_DOWNLOAD_DIR # for demo configs only
750 )
751 USER_DOWNLOAD_FILE_LIFETIME_MIN = 60
752 USER_DOWNLOAD_MAX_SPACE_MB = 100
753 WEBVIEW_LOGLEVEL = logging.INFO
754 WEBVIEW_LOGLEVEL_TEXTFORMAT = "info" # should match WEBVIEW_LOGLEVEL
756 # Not yet user-configurable
757 PLOT_FONTSIZE = 8
759 # [server] section
760 CHERRYPY_LOG_SCREEN = True
761 CHERRYPY_ROOT_PATH = "/"
762 CHERRYPY_SERVER_NAME = "localhost"
763 CHERRYPY_THREADS_MAX = 100
764 CHERRYPY_THREADS_START = 10
765 DEBUG_REVERSE_PROXY = False
766 DEBUG_SHOW_GUNICORN_OPTIONS = False
767 DEBUG_TOOLBAR = False
768 GUNICORN_DEBUG_RELOAD = False
770 if ENVVAR_GENERATING_CAMCOPS_DOCS in os.environ:
771 GUNICORN_NUM_WORKERS = 16
772 else:
773 GUNICORN_NUM_WORKERS = 2 * multiprocessing.cpu_count()
775 GUNICORN_TIMEOUT_S = 30
776 HOST = "127.0.0.1"
777 PORT = Ports.ALTERNATIVE_HTTP_NONSTANDARD
778 PROXY_REWRITE_PATH_INFO = False
779 SHOW_REQUEST_IMMEDIATELY = False
780 SHOW_REQUESTS = False
781 SHOW_RESPONSE = False
782 SHOW_TIMING = False
783 SSL_CERTIFICATE = ""
784 SSL_PRIVATE_KEY = ""
785 STATIC_CACHE_DURATION_S = 1 * 24 * 60 * 60 # 1 day, in seconds = 86400
787 # [export] section
788 CELERY_BROKER_URL = "amqp://"
789 CELERY_BEAT_SCHEDULE_DATABASE = os.path.join(
790 LINUX_DEFAULT_LOCK_DIR, "camcops_celerybeat_schedule"
791 ) # for demo configs only
792 EXPORT_LOCKDIR = LINUX_DEFAULT_LOCK_DIR # for demo configs only
793 SCHEDULE_TIMEZONE = "UTC"
795 # Individual export recipients
796 # DB_ECHO: as above
797 ALL_GROUPS = False
798 DB_ADD_SUMMARIES = True
799 DB_INCLUDE_BLOBS = True
800 DB_PATIENT_ID_PER_ROW = False
801 EMAIL_BODY_IS_HTML = False
802 EMAIL_KEEP_MESSAGE = False
803 FHIR_APP_ID = CAMCOPS_DEFAULT_FHIR_APP_ID
804 FILE_EXPORT_RIO_METADATA = False
805 FILE_MAKE_DIRECTORY = False
806 FILE_OVERWRITE_FILES = False
807 FILE_PATIENT_SPEC_IF_ANONYMOUS = "anonymous"
808 FINALIZED_ONLY = True
809 HL7_DEBUG_DIVERT_TO_FILE = False
810 HL7_DEBUG_TREAT_DIVERTED_AS_SENT = False
811 HL7_KEEP_MESSAGE = False
812 HL7_KEEP_REPLY = False
813 HL7_NETWORK_TIMEOUT_MS = 10000
814 HL7_PING_FIRST = True
815 HL7_PORT = Ports.HL7_MLLP
816 INCLUDE_ANONYMOUS = False
817 PUSH = False
818 REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY = True
819 TASK_FORMAT = FileType.PDF
820 XML_FIELD_COMMENTS = True
822 def __init__(self, docker: bool = False) -> None:
823 """
824 Args:
825 docker:
826 Amend defaults so it works within a Docker Compose application
827 without much fiddling?
829 Defaults for use within Docker:
831 - Note that a URL to another container/service looks like
832 ``protocol://container:port/``. Values here must match the Docker
833 Compose file.
834 """
835 self._docker = docker
836 if docker:
837 self.CELERY_BROKER_URL = DockerConstants.CELERY_BROKER_URL
838 self.CELERY_BEAT_SCHEDULE_DATABASE = os.path.join(
839 DockerConstants.DEFAULT_LOCKDIR, "camcops_celerybeat_schedule"
840 )
841 self.DB_SERVER = "@@db_server@@"
842 self.DB_PORT = "@@db_port@@" # string so that we can substitute
843 self.DB_USER = "@@db_user@@"
844 self.DB_PASSWORD = "@@db_password@@"
845 self.DB_DATABASE = "@@db_database@@"
846 self.EXPORT_LOCKDIR = DockerConstants.DEFAULT_LOCKDIR
847 self.HOST = DockerConstants.HOST
848 self.SSL_CERTIFICATE = "@@ssl_certificate@@"
849 self.SSL_PRIVATE_KEY = "@@ssl_private_key@@"
850 self.USER_DOWNLOAD_DIR = DockerConstants.DEFAULT_USER_DOWNLOAD_DIR
852 @property
853 def demo_db_url(self) -> str:
854 """
855 The demonstration SQLAlchemy URL.
856 """
857 # mysqlclient ("mysqldb") for Docker -- the C-based fast one
858 # pymysql for standard installations -- fewer dependencies
859 driver = "mysqldb" if self._docker else "pymysql"
860 return make_mysql_url(
861 driver=driver,
862 host=self.DB_SERVER,
863 # A bit suboptimal here. cardinal_pythonlib quite understandably
864 # thinks a port should be an int but we want to put in the string
865 # @@db_port@@ to be replaced later in the config file
866 port=cast(int, self.DB_PORT),
867 username=self.DB_USER,
868 password=self.DB_PASSWORD,
869 dbname=self.DB_DATABASE,
870 )
873# =============================================================================
874# String length limits
875# =============================================================================
876#
877# Note: "191" relates to MySQL indexing of VARCHAR fields using utf8mb4;
878# - https://stackoverflow.com/questions/6172798/
879# - https://dev.mysql.com/doc/refman/5.7/en/innodb-restrictions.html
880# There are alternative workarounds, but these fields are OK at 191.
881#
882# Re commenting variables in Sphinx:
883# - https://stackoverflow.com/questions/20227051/how-to-document-a-module-constant-in-python # noqa
884# - http://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-autodata # noqa
885# - URLs are a bit tricky; the colons get re-interpreted sometimes; it seems
886# that inserting an extra colon after "#:", e.g. "#: : see http://somewhere"
887# works.
888# - If a comment needs noqa for the linter, then make it a docstring,
889# because it will appear in the Sphinx string.
892class StringLengths:
893 # -------------------------------------------------------------------------
894 # Primary
895 # -------------------------------------------------------------------------
896 AUDIT_SOURCE_MAX_LEN = 20 #: our choice based on use in CamCOPS code
898 BASE32_MAX_LEN = 32
900 #: : See https://docs.python.org/3.7/library/codecs.html#standard-encodings. # noqa: E501
901 #: Probably ~18 so give it some headroom.
902 CHARSET_MAX_LEN = 64
904 CURRENCY_MAX_LEN = 3 #: Can have Unicode symbols like € or text like "GBP"
906 DATABASE_TITLE_MIN_LEN = 1
907 DATABASE_TITLE_MAX_LEN = 255 #: our choice
909 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index;
910 #: must be compatible with tablet
911 DEVICE_NAME_MAX_LEN = 191
913 #: : See https://en.wikipedia.org/wiki/Email_address.
914 EMAIL_ADDRESS_MAX_LEN = 255
916 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index
917 EXPORT_RECIPIENT_NAME_MIN_LEN = 1
918 EXPORT_RECIPIENT_NAME_MAX_LEN = 191
920 #: Our choice
921 FILTER_TEXT_MAX_LEN = 255
923 #: Our choice; used for user full names on the server
924 FULLNAME_MAX_LEN = 255
926 #: Our choice
927 FILESPEC_MAX_LEN = 255
929 GROUP_DESCRIPTION_MIN_LEN = 1
930 #: Our choice
931 GROUP_DESCRIPTION_MAX_LEN = 255
933 GROUP_NAME_MIN_LEN = 1
934 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index
935 GROUP_NAME_MAX_LEN = 191
937 HASHED_PW_MAX_LEN = 60
938 """
939 :
940 We use ``bcrypt``. Empirically, the length of its hashed output is:
942 .. code-block:: none
944 "$2a$" (4)
945 cost parameter, e.g. "$09" for 9 rounds (3)
946 b64-enc 128-bit salt (22)
947 b64enc 184-bit hash (31)
949 ... total 60
951 See https://stackoverflow.com/questions/5881169/what-column-type-length-should-i-use-for-storing-a-bcrypt-hashed-password-in-a-d
952 """ # noqa
954 HL7_AA_MAX_LEN = 20
955 """
956 - The AA appears in Table 4.6 "Extended composite ID", p46-47 of
957 hl7guide-1-4-2012-08.pdf
958 - ... but is defined in Table 4.9 "Entity Identifier", p50, in which:
960 - component 2 is the Assigning Authority (see component 1)
961 - component 2 is also a Namespace ID with a length of 20
963 - ... and multiple other examples of an Assigning Authority being one
964 example of a Namespace ID
966 - ... and examples are in Table 0363 (p229 of the PDF), which are all
967 3-char.
969 - ... and several other examples of "Namespace ID" being of length 1..20
970 meaning 1-20.
971 """
973 HL7_ID_TYPE_MAX_LEN = 5
974 """
975 Table 4.6 "Extended composite ID", p46-47 of hl7guide-1-4-2012-08.pdf,
976 and Table 0203 "Identifier type", p204 of that PDF, in Appendix B.
977 """
979 HOSTNAME_MAX_LEN = 255
980 """
981 FQDN; see
982 https://stackoverflow.com/questions/8724954/what-is-the-maximum-number-of-characters-for-a-host-name-in-unix
983 """
985 ICD9_CODE_MAX_LEN = 6
986 """
987 Longest is "xxx.xx"; thus, 6; see
988 https://www.cms.gov/Medicare/Quality-Initiatives-Patient-Assessment-Instruments/HospitalQualityInits/Downloads/HospitalAppendix_F.pdf
989 """
991 #: longest is e.g. "F00.000"; "F10.202"; thus, 7
992 ICD10_CODE_MAX_LEN = 7
994 #: Our choice
995 ID_DESCRIPTOR_MAX_LEN = 255
997 #: Our choice
998 ID_POLICY_MAX_LEN = 255
1000 #: : See http://stackoverflow.com/questions/166132
1001 IP_ADDRESS_MAX_LEN = 45
1003 ISO8601_DATETIME_STRING_MAX_LEN = 32
1004 """
1005 Max length e.g.
1007 .. code-block:: none
1009 2013-07-24T20:04:07.123456+01:00
1010 1234567890123456789012345678901234567890
1012 (with punctuation, T, microseconds, colon in timezone).
1013 """
1015 #: See :func:`cardinal_pythonlib.datetimefunc.duration_to_iso`
1016 ISO8601_DURATION_STRING_MAX_LEN = 29
1018 #: Two-letter language, hyphen, 2/3-letter country
1019 LANGUAGE_CODE_MAX_LEN = 6
1021 # LONGBLOB_LONGTEXT_MAX_LEN = (2 ** 32) - 1
1022 # ... https://dev.mysql.com/doc/refman/8.0/en/storage-requirements.html
1024 # The longest is currently "hotp_email" (ViewArg, cc_pyramid.py)
1025 MFA_METHOD_MAX_LEN = 20
1027 #: See https://stackoverflow.com/questions/643690
1028 MIMETYPE_MAX_LEN = 255
1030 #: For forename and surname, each; our choice but must match tablet
1031 PATIENT_NAME_MAX_LEN = 255
1033 PHONE_NUMBER_MAX_LEN = 128
1035 RFC_2822_DATE_MAX_LEN = 31
1036 """
1037 e.g. ``Fri, 09 Nov 2001 01:08:47 -0000``; 3.3 in
1038 https://tools.ietf.org/html/rfc2822, assuming extra white space not added
1039 """
1041 #: for export; our choice based on use in CamCOPS code
1042 SENDING_FORMAT_MAX_LEN = 50
1044 #: our choice; 64 bytes => 512 bits, which is a lot in 2017
1045 SESSION_TOKEN_MAX_BYTES = 64
1047 SQL_SEARCH_LITERAL_MIN_LENGTH = 0 # permits: LIKE ''
1048 SQL_SEARCH_LITERAL_MAX_LENGTH = 255 # arbitrary
1050 TABLENAME_MAX_LEN = 128
1051 """
1052 For
1054 - MySQL: 64 -- https://dev.mysql.com/doc/refman/5.7/en/identifiers.html
1055 - SQL Server: 128 -- https://msdn.microsoft.com/en-us/library/ms191240.aspx
1056 - Oracle: 32, then 128 from v12.2 (2017)
1057 """ # noqa: E501
1059 TASK_SUMMARY_TEXT_FIELD_DEFAULT_MAX_LEN = 50
1060 """
1061 ... our choice, contains short strings like "normal", "abnormal", "severe".
1062 Easy to change, since it's only used when exporting summaries, and not in
1063 the core database.
1064 """
1066 #: Our choice
1067 URL_MAX_LEN = 255
1069 USERNAME_CAMCOPS_MIN_LEN = 1
1070 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index
1071 USERNAME_CAMCOPS_MAX_LEN = 191
1073 #: Our choice
1074 USERNAME_EXTERNAL_MAX_LEN = 255
1076 # -------------------------------------------------------------------------
1077 # Derived
1078 # -------------------------------------------------------------------------
1080 DIAGNOSTIC_CODE_MAX_LEN = max(ICD9_CODE_MAX_LEN, ICD10_CODE_MAX_LEN)
1082 SESSION_TOKEN_MAX_LEN = len(
1083 create_base64encoded_randomness(SESSION_TOKEN_MAX_BYTES)
1084 )
1087# =============================================================================
1088# FHIR string constants
1089# =============================================================================
1092class FHIRConst:
1093 """
1094 Constants used, mainly as dictionary keys, by the Python ``fhirclient``
1095 package.
1097 - Capitalized: usually FHIR object types
1098 - lower_case or lowerCamelCase: usually dictionary keys
1099 - plainlowercase: often string constants
1100 """
1102 # -------------------------------------------------------------------------
1103 # Authentication (FHIRClient settings)
1104 # -------------------------------------------------------------------------
1106 API_BASE = "api_base"
1107 APP_ID = "app_id"
1108 APP_SECRET = "app_secret"
1109 LAUNCH_TOKEN = "launch_token"
1111 # -------------------------------------------------------------------------
1112 # Generic keys (used by lots)
1113 # -------------------------------------------------------------------------
1115 CODE = "code"
1116 DATE = "date"
1117 DESCRIPTION = "description"
1118 IDENTIFIER = "identifier"
1119 ITEM = "item"
1120 NAME = "name"
1121 STATUS = "status"
1122 SUBJECT = "subject"
1123 SYSTEM = "system"
1124 TRANSACTION = "transaction"
1125 URL = "url"
1126 VALUE = "value"
1128 # -------------------------------------------------------------------------
1129 # Resource types (usually: BundleEntryRequest keys)
1130 # -------------------------------------------------------------------------
1132 RESOURCE_TYPE_BUNDLE = "Bundle"
1133 RESOURCE_TYPE_CONDITION = "Condition"
1134 RESOURCE_TYPE_DOCUMENT_REFERENCE = "DocumentReference"
1135 RESOURCE_TYPE_OBSERVATION = "Observation"
1136 RESOURCE_TYPE_PATIENT = "Patient"
1137 RESOURCE_TYPE_PRACTITIONER = "Practitioner"
1138 RESOURCE_TYPE_QUESTIONNAIRE = "Questionnaire"
1139 RESOURCE_TYPE_QUESTIONNAIRE_RESPONSE = "QuestionnaireResponse"
1141 # -------------------------------------------------------------------------
1142 # Resource-specific keys
1143 # -------------------------------------------------------------------------
1145 # Annotation keys
1146 AUTHOR_REFERENCE = "authorReference"
1147 AUTHOR_STRING = "authorString"
1148 TIME = "time"
1150 # Attachment and Binary keys
1151 CONTENT_TYPE = "contentType"
1152 DATA = "data"
1154 # Bundle keys
1155 TYPE = "type"
1156 ENTRY = "entry"
1158 # BundleEntry keys
1159 REQUEST = "request"
1160 RESOURCE = "resource"
1162 # BundleEntryRequest keys
1163 IF_NONE_EXIST = "ifNoneExist"
1164 METHOD = "method"
1166 # CodeableConcept keys
1167 CODING = "coding"
1168 TEXT = "text"
1170 # Coding keys
1171 DISPLAY = "display"
1172 USER_SELECTED = "userSelected"
1173 VERSION = "version"
1174 # Coding values
1175 # https://www.hl7.org/fhir/terminologies-systems.html
1176 # http://www.hl7.org/fhir/snomedct.html
1177 # noinspection HttpUrlsUsage
1178 CODE_SYSTEM_SNOMED_CT = "http://snomed.info/sct"
1179 # http://www.hl7.org/fhir/icd.html
1180 # noinspection HttpUrlsUsage
1181 CODE_SYSTEM_ICD9_CM = "http://hl7.org/fhir/sid/icd-9-cm"
1182 # http://www.hl7.org/fhir/icd.html
1183 # noinspection HttpUrlsUsage
1184 CODE_SYSTEM_ICD10 = "http://hl7.org/fhir/sid/icd-10"
1185 # noinspection HttpUrlsUsage
1186 CODE_SYSTEM_LOINC = "http://loinc.org"
1187 # noinspection HttpUrlsUsage
1188 CODE_SYSTEM_UCUM = "http://unitsofmeasure.org"
1190 # Condition keys
1191 NOTE = "note"
1192 RECORDER = "recorder" # = clinician
1194 # ContactPoint values
1195 TELECOM_SYSTEM_EMAIL = "email"
1196 TELECOM_SYSTEM_OTHER = "other"
1198 # DocumentReference keys
1199 AUTHOR = "author"
1200 CONTENT = "content"
1201 DOCSTATUS = "docStatus"
1202 MASTER_IDENTIFIER = "masterIdentifier"
1203 # DocumentReference values
1204 DOCSTATUS_CURRENT = "current"
1205 DOCSTATUS_FINAL = "final"
1206 DOCSTATUS_PRELIMINARY = "preliminary"
1208 # DocumentReferenceContent keys:
1209 ATTACHMENT = "attachment"
1210 FORMAT = "format"
1212 # HumanName keys
1213 NAME_FAMILY = "family"
1214 NAME_GIVEN = "given"
1216 # Observation keys
1217 COMPONENT = "component"
1218 EFFECTIVE_DATE_TIME = "effectiveDateTime"
1219 # Observation values
1220 OBSSTATUS_FINAL = "final"
1221 OBSSTATUS_PRELIMINARY = "preliminary"
1223 # Observation/ObservationComponent/QuestionnaireResponseItemAnswer keys
1224 # (not all are possible for all of those!).
1225 VALUE_ATTACHMENT = "valueAttachment"
1226 VALUE_BOOLEAN = "valueBoolean"
1227 VALUE_CODEABLE_CONCEPT = "valueCodeableConcept"
1228 VALUE_CODING = "valueCoding"
1229 VALUE_DATE = "valueDate"
1230 VALUE_DATETIME = "valueDateTime"
1231 VALUE_DECIMAL = "valueDecimal"
1232 VALUE_INTEGER = "valueInteger"
1233 VALUE_QUANTITY = "valueQuantity"
1234 VALUE_REFERENCE = "valueReference"
1235 VALUE_STRING = "valueString"
1236 VALUE_TIME = "valueTime"
1237 VALUE_URI = "valueUri"
1239 # Patient/Practitioner keys
1240 ADDRESS = "address"
1241 BIRTHDATE = "birthDate"
1242 GENDER = "gender"
1243 TELECOM = "telecom"
1244 # Patient values
1245 GENDER_FEMALE = "female"
1246 GENDER_MALE = "male"
1247 GENDER_OTHER = "other"
1248 GENDER_UNKNOWN = "unknown"
1250 # Address keys
1251 ADDRESS_TEXT = "text"
1253 # Quantity keys
1254 # COMPARATOR = "comparator"
1255 UNIT = "unit"
1257 # Questionnaire keys
1258 TITLE = "title"
1259 COPYRIGHT = "copyright"
1260 # Questionnaire values
1261 QSTATUS_ACTIVE = "active"
1262 QSTATUS_COMPLETED = "completed"
1263 QSTATUS_IN_PROGRESS = "in-progress"
1264 QSTATUS_STOPPED = "stopped"
1266 # QuestionnaireItem keys
1267 LINK_ID = "linkId"
1268 ANSWER_OPTION = "answerOption"
1269 # NB: answerValueSet isn't just a list; it's a fiddly thing.
1270 # QuestionnaireItem values
1271 QITEM_TYPE_ATTACHMENT = "attachment"
1272 QITEM_TYPE_BOOLEAN = "boolean"
1273 QITEM_TYPE_CHOICE = "choice"
1274 QITEM_TYPE_DATE = "date"
1275 QITEM_TYPE_DATETIME = "dateTime"
1276 QITEM_TYPE_DECIMAL = "decimal"
1277 QITEM_TYPE_DISPLAY = "display"
1278 QITEM_TYPE_GROUP = "group"
1279 QITEM_TYPE_INTEGER = "integer"
1280 QITEM_TYPE_OPEN_CHOICE = "open-choice"
1281 QITEM_TYPE_QUANTITY = "quantity"
1282 QITEM_TYPE_QUESTION = "question"
1283 QITEM_TYPE_REFERENCE = "reference"
1284 QITEM_TYPE_STRING = "string"
1285 QITEM_TYPE_TIME = "time"
1286 QITEM_TYPE_URL = "url"
1287 # Some belong here but are not in the fhirclient docs. See:
1288 # https://www.hl7.org/fhir/codesystem-item-type.html
1289 # https://www.hl7.org/fhir/valueset-item-type.html
1291 # QuestionnaireResponse keys
1292 AUTHORED = "authored"
1293 QUESTIONNAIRE = "questionnaire"
1295 # QuestionnaireResponseItem keys
1296 ANSWER = "answer"
1298 # -------------------------------------------------------------------------
1299 # Very specific codes
1300 # -------------------------------------------------------------------------
1302 # For BMI and related:
1303 # - https://www.hl7.org/fhir/observation-example-bmi-using-related.html
1304 # - https://www.hl7.org/fhir/observation-example.html
1305 # - https://hl7.org/fhir/us/core/2017Jan/ValueSet-us-core-ucum.html
1306 # - Height: https://loinc.org/8302-2/
1307 # - Waist circumference: https://loinc.org/8280-0/ -- but NB also several
1308 # others. https://loinc.org/56117-5/ is the "natural waist" which is most
1309 # likely in the absence of other detail.
1310 LOINC_BMI_CODE = "39156-5"
1311 LOINC_BMI_TEXT = "Body mass index (BMI) [Ratio]"
1312 LOINC_BODY_WEIGHT_CODE = "29463-7"
1313 LOINC_BODY_WEIGHT_TEXT = "Body weight"
1314 LOINC_HEIGHT_CODE = "8302-2"
1315 LOINC_HEIGHT_TEXT = "Body height"
1316 LOINC_WAIST_CIRCUMFERENCE_CODE = "56117-5"
1317 LOINC_WAIST_CIRCUMFERENCE_TEXT = "Waist Circumference by WHI"
1318 UCUM_CODE_KG_PER_SQ_M = "kg/m2"
1319 UCUM_CODE_KG = "kg"
1320 UCUM_CODE_METRE = "m"
1321 UCUM_CODE_CENTIMETRE = "cm"
1322 # noinspection HttpUrlsUsage
1323 VITAL_SIGNS_SYSTEM = (
1324 "http://terminology.hl7.org/CodeSystem/observation-category"
1325 )
1326 VITAL_SIGNS_CODE = "vital-signs"
1327 VITAL_SIGNS_DISPLAY = "Vital Signs"
1329 # ID_SYSTEM_NHS_NUMBER = "https://fhir.nhs.uk/Id/nhs-number"
1331 # -------------------------------------------------------------------------
1332 # Response values
1333 # -------------------------------------------------------------------------
1335 ETAG = "etag"
1336 ID = "id"
1337 LAST_MODIFIED = "lastModified"
1338 LINK = "link"
1339 LOCATION = "location"
1340 RELATION = "relation"
1341 RESOURCE_TYPE = "resourceType"
1342 RESPONSE = "response"
1343 SELF = "self"
1344 TRANSACTION_RESPONSE = "transaction-response"
1345 RESPONSE_STATUS_200_OK = "200 OK"
1346 RESPONSE_STATUS_201_CREATED = "201 Created"
1348 # -------------------------------------------------------------------------
1349 # CamCOPS tags
1350 # -------------------------------------------------------------------------
1352 CAMCOPS_VALUE_CLINICIAN_WITHIN_TASK = "clinician"
1353 CAMCOPS_VALUE_PATIENT_WITHIN_TASK = "patient"
1354 CAMCOPS_VALUE_QUESTIONNAIRE_RESPONSE_WITHIN_TASK = "qr"