Coverage for cc_modules/cc_constants.py: 97%
653 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_constants.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**Various constants.**
30"""
32# Helpful UTF-8 characters: ‘’ “” – — × • ≤ ≥ ≠ ± →
34import logging
35import multiprocessing
36import os
38from cardinal_pythonlib.randomness import create_base64encoded_randomness
39from cardinal_pythonlib.sqlalchemy.session import make_mysql_url
40from cardinal_pythonlib.tcpipconst import Ports
41from cardinal_pythonlib.uriconst import UriSchemes
43from camcops_server.cc_modules.cc_baseconstants import (
44 DEFAULT_EXTRA_STRINGS_DIR,
45 ENVVAR_GENERATING_CAMCOPS_DOCS,
46 LINUX_DEFAULT_LOCK_DIR,
47 LINUX_DEFAULT_USER_DOWNLOAD_DIR,
48 STATIC_ROOT_DIR,
49)
50from camcops_server.cc_modules.cc_language import DEFAULT_LOCALE
53# =============================================================================
54# Number of ID numbers. Don't alter this lightly; influences database fields.
55# =============================================================================
57NUMBER_OF_IDNUMS_DEFUNCT = 8 # DEFUNCT BUT DO NOT REMOVE OR ALTER. EIGHT.
58# ... In older versions: determined number of ID number fields.
59# (Now this is arbitrary.) Still used to support old clients.
62# =============================================================================
63# File types
64# =============================================================================
67class FileType(object):
68 """
69 Used to represent output formats and their file extensions.
70 """
72 HTML = "html"
73 PDF = "pdf"
74 XML = "xml"
77# =============================================================================
78# Encodings
79# =============================================================================
81ASCII = "ascii"
82UTF8 = "utf8"
85# =============================================================================
86# Launching
87# =============================================================================
89DEFAULT_FLOWER_ADDRESS = "127.0.0.1"
90DEFAULT_FLOWER_PORT = (
91 5555 # http://docs.celeryproject.org/en/latest/userguide/monitoring.html
92)
95# =============================================================================
96# Webview constants
97# =============================================================================
99DEFAULT_ROWS_PER_PAGE = 25
100DEVICE_NAME_FOR_SERVER = "server" # Do not alter.
101USER_NAME_FOR_SYSTEM = "system" # Do not alter.
103# Address to download Windows/Mac versions:
104GITHUB_RELEASES_URL = (
105 "https://github.com/ucam-department-of-psychiatry/camcops/releases/"
106)
108MINIMUM_PASSWORD_LENGTH = 10
110OBSCURE_PHONE_ASTERISKS = "*" * 10
111OBSCURE_EMAIL_ASTERISKS = "*" * 5
114# =============================================================================
115# Other display constants
116# =============================================================================
118JSON_INDENT = 4
121# =============================================================================
122# Date formats
123# =============================================================================
126class DateFormat(object):
127 """
128 Assorted date/time formats.
129 """
131 SHORT_DATE = "%d %b %Y" # e.g. 24 Jul 2013
132 LONG_DATE = "%d %B %Y" # e.g. 24 July 2013
133 LONG_DATE_WITH_DAY = "%a %d %B %Y" # e.g. Wed 24 July 2013
134 LONG_DATETIME = "%d %B %Y, %H:%M %z" # e.g. 24 July 2013, 20:04 +0100
135 LONG_DATETIME_WITH_DAY = (
136 "%a %d %B %Y, %H:%M %z" # e.g. Wed 24 July 2013, 20:04 +0100
137 )
138 LONG_DATETIME_WITH_DAY_NO_TZ = (
139 "%a %d %B %Y, %H:%M" # e.g. Wed 24 July 2013, 20:04
140 )
141 SHORT_DATETIME_WITH_DAY_NO_TZ = (
142 "%a %d %b %Y, %H:%M" # e.g. Wed 24 Jul 2013, 20:04
143 )
144 LONG_DATETIME_SECONDS = "%d %B %Y, %H:%M:%S %z"
145 SHORT_DATETIME = "%d %b %Y, %H:%M %z" # e.g. 24 Jul 2013, 20:04 +0100
146 SHORT_DATETIME_NO_TZ = "%d %b %Y, %H:%M" # e.g. 24 Jul 2013, 20:04
147 SHORT_DATETIME_SECONDS = (
148 "%d %b %Y, %H:%M:%S %z" # e.g. 24 Jul 2013, 20:04:23 +0100
149 )
150 HOURS_MINUTES = "%H:%M" # e.g. 20:04
151 ISO8601 = "%Y-%m-%dT%H:%M:%S%z" # e.g. 2013-07-24T20:04:07+0100
152 ISO8601_HUMANIZED_TO_MINUTES = "%Y-%m-%d %H:%M" # e.g. 2013-07-24 20:04
153 ISO8601_HUMANIZED_TO_SECONDS = (
154 "%Y-%m-%d %H:%M:%S" # e.g. 2013-07-24 20:04:23
155 )
156 ISO8601_HUMANIZED_TO_SECONDS_TZ = (
157 "%Y-%m-%d %H:%M:%S %z" # e.g. 2013-07-24 20:04:23 +0100
158 )
159 ISO8601_DATE_ONLY = "%Y-%m-%d" # e.g. 2013-07-24
160 FHIR_DATE = "%Y-%m-%d" # e.g. 2013-07-24
161 # FHIR_DATETIME -- use x.isoformat()
162 FHIR_TIME = "%H:%M:%S" # https://www.hl7.org/fhir/codesystem-item-type.html#item-type-time # noqa: E501
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 = 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@@"
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 port=self.DB_PORT,
864 username=self.DB_USER,
865 password=self.DB_PASSWORD,
866 dbname=self.DB_DATABASE,
867 )
870# =============================================================================
871# String length limits
872# =============================================================================
873#
874# Note: "191" relates to MySQL indexing of VARCHAR fields using utf8mb4;
875# - https://stackoverflow.com/questions/6172798/
876# - https://dev.mysql.com/doc/refman/5.7/en/innodb-restrictions.html
877# There are alternative workarounds, but these fields are OK at 191.
878#
879# Re commenting variables in Sphinx:
880# - https://stackoverflow.com/questions/20227051/how-to-document-a-module-constant-in-python # noqa
881# - http://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-autodata # noqa
882# - URLs are a bit tricky; the colons get re-interpreted sometimes; it seems
883# that inserting an extra colon after "#:", e.g. "#: : see http://somewhere"
884# works.
885# - If a comment needs "# noqa" for the linter, then make it a docstring,
886# because it will appear in the Sphinx string.
889class StringLengths:
890 # -------------------------------------------------------------------------
891 # Primary
892 # -------------------------------------------------------------------------
893 AUDIT_SOURCE_MAX_LEN = 20 #: our choice based on use in CamCOPS code
895 BASE32_MAX_LEN = 32
897 #: : See https://docs.python.org/3.7/library/codecs.html#standard-encodings. # noqa: E501
898 #: Probably ~18 so give it some headroom.
899 CHARSET_MAX_LEN = 64
901 CURRENCY_MAX_LEN = 3 #: Can have Unicode symbols like € or text like "GBP"
903 DATABASE_TITLE_MIN_LEN = 1
904 DATABASE_TITLE_MAX_LEN = 255 #: our choice
906 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index;
907 #: must be compatible with tablet
908 DEVICE_NAME_MAX_LEN = 191
910 #: : See https://en.wikipedia.org/wiki/Email_address.
911 EMAIL_ADDRESS_MAX_LEN = 255
913 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index
914 EXPORT_RECIPIENT_NAME_MIN_LEN = 1
915 EXPORT_RECIPIENT_NAME_MAX_LEN = 191
917 #: Our choice
918 FILTER_TEXT_MAX_LEN = 255
920 #: Our choice; used for user full names on the server
921 FULLNAME_MAX_LEN = 255
923 #: Our choice
924 FILESPEC_MAX_LEN = 255
926 GROUP_DESCRIPTION_MIN_LEN = 1
927 #: Our choice
928 GROUP_DESCRIPTION_MAX_LEN = 255
930 GROUP_NAME_MIN_LEN = 1
931 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index
932 GROUP_NAME_MAX_LEN = 191
934 HASHED_PW_MAX_LEN = 60
935 """
936 :
937 We use ``bcrypt``. Empirically, the length of its hashed output is:
939 .. code-block:: none
941 "$2a$" (4)
942 cost parameter, e.g. "$09" for 9 rounds (3)
943 b64-enc 128-bit salt (22)
944 b64enc 184-bit hash (31)
946 ... total 60
948 See https://stackoverflow.com/questions/5881169/what-column-type-length-should-i-use-for-storing-a-bcrypt-hashed-password-in-a-d
949 """ # noqa
951 HL7_AA_MAX_LEN = 20
952 """
953 - The AA appears in Table 4.6 "Extended composite ID", p46-47 of
954 hl7guide-1-4-2012-08.pdf
955 - ... but is defined in Table 4.9 "Entity Identifier", p50, in which:
957 - component 2 is the Assigning Authority (see component 1)
958 - component 2 is also a Namespace ID with a length of 20
960 - ... and multiple other examples of an Assigning Authority being one
961 example of a Namespace ID
963 - ... and examples are in Table 0363 (p229 of the PDF), which are all
964 3-char.
966 - ... and several other examples of "Namespace ID" being of length 1..20
967 meaning 1-20.
968 """
970 HL7_ID_TYPE_MAX_LEN = 5
971 """
972 Table 4.6 "Extended composite ID", p46-47 of hl7guide-1-4-2012-08.pdf,
973 and Table 0203 "Identifier type", p204 of that PDF, in Appendix B.
974 """
976 HOSTNAME_MAX_LEN = 255
977 """
978 FQDN; see
979 https://stackoverflow.com/questions/8724954/what-is-the-maximum-number-of-characters-for-a-host-name-in-unix
980 """ # noqa
982 ICD9_CODE_MAX_LEN = 6
983 """
984 Longest is "xxx.xx"; thus, 6; see
985 https://www.cms.gov/Medicare/Quality-Initiatives-Patient-Assessment-Instruments/HospitalQualityInits/Downloads/HospitalAppendix_F.pdf
986 """ # noqa
988 #: longest is e.g. "F00.000"; "F10.202"; thus, 7
989 ICD10_CODE_MAX_LEN = 7
991 #: Our choice
992 ID_DESCRIPTOR_MAX_LEN = 255
994 #: Our choice
995 ID_POLICY_MAX_LEN = 255
997 #: : See http://stackoverflow.com/questions/166132
998 IP_ADDRESS_MAX_LEN = 45
1000 ISO8601_DATETIME_STRING_MAX_LEN = 32
1001 """
1002 Max length e.g.
1004 .. code-block:: none
1006 2013-07-24T20:04:07.123456+01:00
1007 1234567890123456789012345678901234567890
1009 (with punctuation, T, microseconds, colon in timezone).
1010 """
1012 #: See :func:`cardinal_pythonlib.datetimefunc.duration_to_iso`
1013 ISO8601_DURATION_STRING_MAX_LEN = 29
1015 #: Two-letter language, hyphen, 2/3-letter country
1016 LANGUAGE_CODE_MAX_LEN = 6
1018 # LONGBLOB_LONGTEXT_MAX_LEN = (2 ** 32) - 1
1019 # ... https://dev.mysql.com/doc/refman/8.0/en/storage-requirements.html
1021 # The longest is currently "hotp_email" (ViewArg, cc_pyramid.py)
1022 MFA_METHOD_MAX_LEN = 20
1024 #: See https://stackoverflow.com/questions/643690
1025 MIMETYPE_MAX_LEN = 255
1027 #: For forename and surname, each; our choice but must match tablet
1028 PATIENT_NAME_MAX_LEN = 255
1030 PHONE_NUMBER_MAX_LEN = 128
1032 RFC_2822_DATE_MAX_LEN = 31
1033 """
1034 e.g. ``Fri, 09 Nov 2001 01:08:47 -0000``; 3.3 in
1035 https://tools.ietf.org/html/rfc2822, assuming extra white space not added
1036 """
1038 #: for export; our choice based on use in CamCOPS code
1039 SENDING_FORMAT_MAX_LEN = 50
1041 #: our choice; 64 bytes => 512 bits, which is a lot in 2017
1042 SESSION_TOKEN_MAX_BYTES = 64
1044 SQL_SEARCH_LITERAL_MIN_LENGTH = 0 # permits: LIKE ''
1045 SQL_SEARCH_LITERAL_MAX_LENGTH = 255 # arbitrary
1047 TABLENAME_MAX_LEN = 128
1048 """
1049 For
1051 - MySQL: 64 -- https://dev.mysql.com/doc/refman/5.7/en/identifiers.html
1052 - SQL Server: 128 -- https://msdn.microsoft.com/en-us/library/ms191240.aspx
1053 - Oracle: 32, then 128 from v12.2 (2017)
1054 """ # noqa: E501
1056 TASK_SUMMARY_TEXT_FIELD_DEFAULT_MAX_LEN = 50
1057 """
1058 ... our choice, contains short strings like "normal", "abnormal", "severe".
1059 Easy to change, since it's only used when exporting summaries, and not in
1060 the core database.
1061 """
1063 #: Our choice
1064 URL_MAX_LEN = 255
1066 USERNAME_CAMCOPS_MIN_LEN = 1
1067 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index
1068 USERNAME_CAMCOPS_MAX_LEN = 191
1070 #: Our choice
1071 USERNAME_EXTERNAL_MAX_LEN = 255
1073 # -------------------------------------------------------------------------
1074 # Derived
1075 # -------------------------------------------------------------------------
1077 DIAGNOSTIC_CODE_MAX_LEN = max(ICD9_CODE_MAX_LEN, ICD10_CODE_MAX_LEN)
1079 SESSION_TOKEN_MAX_LEN = len(
1080 create_base64encoded_randomness(SESSION_TOKEN_MAX_BYTES)
1081 )
1084# =============================================================================
1085# FHIR string constants
1086# =============================================================================
1089class FHIRConst:
1090 """
1091 Constants used, mainly as dictionary keys, by the Python ``fhirclient``
1092 package.
1094 - Capitalized: usually FHIR object types
1095 - lower_case or lowerCamelCase: usually dictionary keys
1096 - plainlowercase: often string constants
1097 """
1099 # -------------------------------------------------------------------------
1100 # Authentication (FHIRClient settings)
1101 # -------------------------------------------------------------------------
1103 API_BASE = "api_base"
1104 APP_ID = "app_id"
1105 APP_SECRET = "app_secret"
1106 LAUNCH_TOKEN = "launch_token"
1108 # -------------------------------------------------------------------------
1109 # Generic keys (used by lots)
1110 # -------------------------------------------------------------------------
1112 CODE = "code"
1113 DATE = "date"
1114 DESCRIPTION = "description"
1115 IDENTIFIER = "identifier"
1116 ITEM = "item"
1117 NAME = "name"
1118 STATUS = "status"
1119 SUBJECT = "subject"
1120 SYSTEM = "system"
1121 TRANSACTION = "transaction"
1122 URL = "url"
1123 VALUE = "value"
1125 # -------------------------------------------------------------------------
1126 # Resource types (usually: BundleEntryRequest keys)
1127 # -------------------------------------------------------------------------
1129 RESOURCE_TYPE_BUNDLE = "Bundle"
1130 RESOURCE_TYPE_CONDITION = "Condition"
1131 RESOURCE_TYPE_DOCUMENT_REFERENCE = "DocumentReference"
1132 RESOURCE_TYPE_OBSERVATION = "Observation"
1133 RESOURCE_TYPE_PATIENT = "Patient"
1134 RESOURCE_TYPE_PRACTITIONER = "Practitioner"
1135 RESOURCE_TYPE_QUESTIONNAIRE = "Questionnaire"
1136 RESOURCE_TYPE_QUESTIONNAIRE_RESPONSE = "QuestionnaireResponse"
1138 # -------------------------------------------------------------------------
1139 # Resource-specific keys
1140 # -------------------------------------------------------------------------
1142 # Annotation keys
1143 AUTHOR_REFERENCE = "authorReference"
1144 AUTHOR_STRING = "authorString"
1145 TIME = "time"
1147 # Attachment and Binary keys
1148 CONTENT_TYPE = "contentType"
1149 DATA = "data"
1151 # Bundle keys
1152 TYPE = "type"
1153 ENTRY = "entry"
1155 # BundleEntry keys
1156 REQUEST = "request"
1157 RESOURCE = "resource"
1159 # BundleEntryRequest keys
1160 IF_NONE_EXIST = "ifNoneExist"
1161 METHOD = "method"
1163 # CodeableConcept keys
1164 CODING = "coding"
1165 TEXT = "text"
1167 # Coding keys
1168 DISPLAY = "display"
1169 USER_SELECTED = "userSelected"
1170 VERSION = "version"
1171 # Coding values
1172 # https://www.hl7.org/fhir/terminologies-systems.html
1173 # http://www.hl7.org/fhir/snomedct.html
1174 # noinspection HttpUrlsUsage
1175 CODE_SYSTEM_SNOMED_CT = "http://snomed.info/sct"
1176 # http://www.hl7.org/fhir/icd.html
1177 # noinspection HttpUrlsUsage
1178 CODE_SYSTEM_ICD9_CM = "http://hl7.org/fhir/sid/icd-9-cm"
1179 # http://www.hl7.org/fhir/icd.html
1180 # noinspection HttpUrlsUsage
1181 CODE_SYSTEM_ICD10 = "http://hl7.org/fhir/sid/icd-10"
1182 # noinspection HttpUrlsUsage
1183 CODE_SYSTEM_LOINC = "http://loinc.org"
1184 # noinspection HttpUrlsUsage
1185 CODE_SYSTEM_UCUM = "http://unitsofmeasure.org"
1187 # Condition keys
1188 NOTE = "note"
1189 RECORDER = "recorder" # = clinician
1191 # ContactPoint values
1192 TELECOM_SYSTEM_EMAIL = "email"
1193 TELECOM_SYSTEM_OTHER = "other"
1195 # DocumentReference keys
1196 AUTHOR = "author"
1197 CONTENT = "content"
1198 DOCSTATUS = "docStatus"
1199 MASTER_IDENTIFIER = "masterIdentifier"
1200 # DocumentReference values
1201 DOCSTATUS_CURRENT = "current"
1202 DOCSTATUS_FINAL = "final"
1203 DOCSTATUS_PRELIMINARY = "preliminary"
1205 # DocumentReferenceContent keys:
1206 ATTACHMENT = "attachment"
1207 FORMAT = "format"
1209 # HumanName keys
1210 NAME_FAMILY = "family"
1211 NAME_GIVEN = "given"
1213 # Observation keys
1214 COMPONENT = "component"
1215 EFFECTIVE_DATE_TIME = "effectiveDateTime"
1216 # Observation values
1217 OBSSTATUS_FINAL = "final"
1218 OBSSTATUS_PRELIMINARY = "preliminary"
1220 # Observation/ObservationComponent/QuestionnaireResponseItemAnswer keys
1221 # (not all are possible for all of those!).
1222 VALUE_ATTACHMENT = "valueAttachment"
1223 VALUE_BOOLEAN = "valueBoolean"
1224 VALUE_CODEABLE_CONCEPT = "valueCodeableConcept"
1225 VALUE_CODING = "valueCoding"
1226 VALUE_DATE = "valueDate"
1227 VALUE_DATETIME = "valueDateTime"
1228 VALUE_DECIMAL = "valueDecimal"
1229 VALUE_INTEGER = "valueInteger"
1230 VALUE_QUANTITY = "valueQuantity"
1231 VALUE_REFERENCE = "valueReference"
1232 VALUE_STRING = "valueString"
1233 VALUE_TIME = "valueTime"
1234 VALUE_URI = "valueUri"
1236 # Patient/Practitioner keys
1237 ADDRESS = "address"
1238 BIRTHDATE = "birthDate"
1239 GENDER = "gender"
1240 TELECOM = "telecom"
1241 # Patient values
1242 GENDER_FEMALE = "female"
1243 GENDER_MALE = "male"
1244 GENDER_OTHER = "other"
1245 GENDER_UNKNOWN = "unknown"
1247 # Address keys
1248 ADDRESS_TEXT = "text"
1250 # Quantity keys
1251 # COMPARATOR = "comparator"
1252 UNIT = "unit"
1254 # Questionnaire keys
1255 TITLE = "title"
1256 COPYRIGHT = "copyright"
1257 # Questionnaire values
1258 QSTATUS_ACTIVE = "active"
1259 QSTATUS_COMPLETED = "completed"
1260 QSTATUS_IN_PROGRESS = "in-progress"
1261 QSTATUS_STOPPED = "stopped"
1263 # QuestionnaireItem keys
1264 LINK_ID = "linkId"
1265 ANSWER_OPTION = "answerOption"
1266 # NB: answerValueSet isn't just a list; it's a fiddly thing.
1267 # QuestionnaireItem values
1268 QITEM_TYPE_ATTACHMENT = "attachment"
1269 QITEM_TYPE_BOOLEAN = "boolean"
1270 QITEM_TYPE_CHOICE = "choice"
1271 QITEM_TYPE_DATE = "date"
1272 QITEM_TYPE_DATETIME = "dateTime"
1273 QITEM_TYPE_DECIMAL = "decimal"
1274 QITEM_TYPE_DISPLAY = "display"
1275 QITEM_TYPE_GROUP = "group"
1276 QITEM_TYPE_INTEGER = "integer"
1277 QITEM_TYPE_OPEN_CHOICE = "open-choice"
1278 QITEM_TYPE_QUANTITY = "quantity"
1279 QITEM_TYPE_QUESTION = "question"
1280 QITEM_TYPE_REFERENCE = "reference"
1281 QITEM_TYPE_STRING = "string"
1282 QITEM_TYPE_TIME = "time"
1283 QITEM_TYPE_URL = "url"
1284 # Some belong here but are not in the fhirclient docs. See:
1285 # https://www.hl7.org/fhir/codesystem-item-type.html
1286 # https://www.hl7.org/fhir/valueset-item-type.html
1288 # QuestionnaireResponse keys
1289 AUTHORED = "authored"
1290 QUESTIONNAIRE = "questionnaire"
1292 # QuestionnaireResponseItem keys
1293 ANSWER = "answer"
1295 # -------------------------------------------------------------------------
1296 # Very specific codes
1297 # -------------------------------------------------------------------------
1299 # For BMI and related:
1300 # - https://www.hl7.org/fhir/observation-example-bmi-using-related.html
1301 # - https://www.hl7.org/fhir/observation-example.html
1302 # - https://hl7.org/fhir/us/core/2017Jan/ValueSet-us-core-ucum.html
1303 # - Height: https://loinc.org/8302-2/
1304 # - Waist circumference: https://loinc.org/8280-0/ -- but NB also several
1305 # others. https://loinc.org/56117-5/ is the "natural waist" which is most
1306 # likely in the absence of other detail.
1307 LOINC_BMI_CODE = "39156-5"
1308 LOINC_BMI_TEXT = "Body mass index (BMI) [Ratio]"
1309 LOINC_BODY_WEIGHT_CODE = "29463-7"
1310 LOINC_BODY_WEIGHT_TEXT = "Body weight"
1311 LOINC_HEIGHT_CODE = "8302-2"
1312 LOINC_HEIGHT_TEXT = "Body height"
1313 LOINC_WAIST_CIRCUMFERENCE_CODE = "56117-5"
1314 LOINC_WAIST_CIRCUMFERENCE_TEXT = "Waist Circumference by WHI"
1315 UCUM_CODE_KG_PER_SQ_M = "kg/m2"
1316 UCUM_CODE_KG = "kg"
1317 UCUM_CODE_METRE = "m"
1318 UCUM_CODE_CENTIMETRE = "cm"
1319 # noinspection HttpUrlsUsage
1320 VITAL_SIGNS_SYSTEM = (
1321 "http://terminology.hl7.org/CodeSystem/observation-category"
1322 )
1323 VITAL_SIGNS_CODE = "vital-signs"
1324 VITAL_SIGNS_DISPLAY = "Vital Signs"
1326 # ID_SYSTEM_NHS_NUMBER = "https://fhir.nhs.uk/Id/nhs-number"
1328 # -------------------------------------------------------------------------
1329 # Response values
1330 # -------------------------------------------------------------------------
1332 ETAG = "etag"
1333 ID = "id"
1334 LAST_MODIFIED = "lastModified"
1335 LINK = "link"
1336 LOCATION = "location"
1337 RELATION = "relation"
1338 RESOURCE_TYPE = "resourceType"
1339 RESPONSE = "response"
1340 SELF = "self"
1341 TRANSACTION_RESPONSE = "transaction-response"
1342 RESPONSE_STATUS_200_OK = "200 OK"
1343 RESPONSE_STATUS_201_CREATED = "201 Created"
1345 # -------------------------------------------------------------------------
1346 # CamCOPS tags
1347 # -------------------------------------------------------------------------
1349 CAMCOPS_VALUE_CLINICIAN_WITHIN_TASK = "clinician"
1350 CAMCOPS_VALUE_PATIENT_WITHIN_TASK = "patient"
1351 CAMCOPS_VALUE_QUESTIONNAIRE_RESPONSE_WITHIN_TASK = "qr"