Coverage for cc_modules/cc_constants.py: 97%

1"""

2 camcops_server/cc_modules/cc_constants.py

3

4===============================================================================

5

6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

8

9 This file is part of CamCOPS.

10

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.

15

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.

20

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/>.

23

24===============================================================================

25

26**Various constants.**

27

28"""

29

30# Helpful UTF-8 characters: ‘’ “” – — × • ≤ ≥ ≠ ± →

31

32 import logging

33 import multiprocessing

34 import os

35 from typing import cast

36

37 from cardinal_pythonlib.randomness import create_base64encoded_randomness

38 from cardinal_pythonlib.sqlalchemy.session import make_mysql_url

39 from cardinal_pythonlib.tcpipconst import Ports

40 from cardinal_pythonlib.uriconst import UriSchemes

41

42 from 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)

49 from camcops_server.cc_modules.cc_language import DEFAULT_LOCALE

50

51

52# =============================================================================

53# Number of ID numbers. Don't alter this lightly; influences database fields.

54# =============================================================================

55

56 NUMBER_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.

59

60

61# =============================================================================

62# File types

63# =============================================================================

64

65

66 class FileType(object):

67 """

68 Used to represent output formats and their file extensions.

69 """

70

71 HTML = "html"

72 PDF = "pdf"

73 XML = "xml"

74

75

76# =============================================================================

77# Encodings

78# =============================================================================

79

80 ASCII = "ascii"

81 UTF8 = "utf8"

82

83

84# =============================================================================

85# Launching

86# =============================================================================

87

88 DEFAULT_FLOWER_ADDRESS = "127.0.0.1"

89 DEFAULT_FLOWER_PORT = (

90 5555 # http://docs.celeryproject.org/en/latest/userguide/monitoring.html

91)

92

93

94# =============================================================================

95# Webview constants

96# =============================================================================

97

98 DEFAULT_ROWS_PER_PAGE = 25

99 DEVICE_NAME_FOR_SERVER = "server" # Do not alter.

100 USER_NAME_FOR_SYSTEM = "system" # Do not alter.

101

102# Address to download Windows/Mac versions:

103 GITHUB_RELEASES_URL = (

104 "https://github.com/ucam-department-of-psychiatry/camcops/releases/"

105)

106

107 MINIMUM_PASSWORD_LENGTH = 10

108

109 OBSCURE_PHONE_ASTERISKS = "*" * 10

110 OBSCURE_EMAIL_ASTERISKS = "*" * 5

111

112

113# =============================================================================

114# Other display constants

115# =============================================================================

116

117 JSON_INDENT = 4

118

119

120# =============================================================================

121# Date formats

122# =============================================================================

123

124

125 class DateFormat(object):

126 """

127 Assorted date/time formats.

128 """

129

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

170

171

172# =============================================================================

173# FHIR constants

174# =============================================================================

175

176 CAMCOPS_DEFAULT_FHIR_APP_ID = "camcops"

177

178

179# =============================================================================

180# Permitted values in fields: some common settings

181# =============================================================================

182

183

184 class PV(object):

185 """

186 Collections of permitted values.

187 """

188

189 BIT = (0, 1)

190

191 # Red/Yellow/Green

192 RYG = ("R", "Y", "G")

193

194

195 NO_CHAR = "N"

196 YES_CHAR = "Y"

197

198# Database values:

199 SEX_FEMALE = "F"

200 SEX_MALE = "M"

201 SEX_OTHER_UNSPECIFIED = "X"

202 POSSIBLE_SEX_VALUES = (SEX_FEMALE, SEX_MALE, SEX_OTHER_UNSPECIFIED)

203

204

205# =============================================================================

206# Field names/specifications

207# =============================================================================

208

209# Do not alter these!

210 TABLET_ID_FIELD = "id"

211 MOVE_OFF_TABLET_FIELD = "_move_off_tablet"

212 CLIENT_DATE_FIELD = "when_last_modified"

213

214# Used for old client support, and TSV field names etc.:

215 FP_ID_NUM = "idnum"

216 FP_ID_DESC = "iddesc"

217 FP_ID_SHORT_DESC = "idshortdesc"

218

219# Additional fields for some exports:

220 EXTRA_IDNUM_FIELD_PREFIX = "_patient_idnum"

221 EXTRA_TASK_TABLENAME_FIELD = "_task_tablename"

222 EXTRA_TASK_SERVER_PK_FIELD = "_task_pk"

223 EXTRA_COMMENT_PREFIX = "(EXTRA) "

224

225

226# =============================================================================

227# Other special values

228# =============================================================================

229

230 CAMCOPS_URL = "https://camcops.readthedocs.io/"

231 ERA_NOW = "NOW" # defines the current era in database records

232

233

234# =============================================================================

235# PDF engine: now always "pdfkit".

236# =============================================================================

237

238# PDF_ENGINE = "xhtml2pdf" # working

239 PDF_ENGINE = "pdfkit" # working

240# PDF_ENGINE = "weasyprint" # working but table <tr> element bugs

241# ... value must be one of: xhtml2pdf, weasyprint, pdfkit

242

243

244# =============================================================================

245# Simple constants for HTML/plots/display

246# =============================================================================

247

248

249 class PlotDefaults(object):

250 """

251 Defaults used with matplotlib plotting.

252 """

253

254 DEFAULT_PLOT_DPI = 300

255

256 FULLWIDTH_PLOT_WIDTH = 6.7 # inches: full width is ~170mm

257

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

270

271

272 class MatplotlibConstants(object):

273 """

274 Constants used by matplotlib

275 """

276

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"

284

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"

290

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 = "*"

296

297 WHOLE_PANEL = 111 # as in: ax = fig.add_subplot(111)

298

299

300# Debugging option

301 USE_SVG_IN_HTML = True # set to False for PNG debugging

302

303

304# =============================================================================

305# CSS/HTML constants

306# =============================================================================

307

308 CSS_PAGED_MEDIA = PDF_ENGINE != "pdfkit"

309

310 WKHTMLTOPDF_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}

323

324

325 class CssClass(object):

326 """

327 CSS names.

328

329 Values should match e.g. ``camcops_server/templates/css/css_base.mako``.

330 """

331

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"

395

396

397# =============================================================================

398# Task constants

399# =============================================================================

400

401 ANON_PATIENT = "XXXX"

402 DATA_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"""

409 DATA_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"""

420 ICD10_COPYRIGHT_DIV = """

421 <div class="copyright">

422 ICD-10 criteria: Copyright © 1992 World Health Organization.

423 Used here with permission.

424 </div>

425"""

426 INVALID_VALUE = "[invalid_value]"

427

428 QUESTION = "Question"

429

430 SPREADSHEET_PATIENT_FIELD_PREFIX = "_patient_"

431

432

433# =============================================================================

434# Config constants

435# =============================================================================

436

437 CONFIG_FILE_SITE_SECTION = "site"

438 CONFIG_FILE_SERVER_SECTION = "server"

439 CONFIG_FILE_EXPORT_SECTION = "export"

440 CONFIG_FILE_SMS_BACKEND_PREFIX = "sms_backend"

441

442

443 class ConfigParamSite(object):

444 """

445 Parameters allowed in the main ``[site]`` section of the CamCOPS config

446 file.

447 """

448

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"

493

494

495 class ConfigParamServer(object):

496 """

497 Parameters allowed in the web server (``[server]``) section of the CamCOPS

498 config file.

499 """

500

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"

534

535

536 class ConfigParamExportGeneral(object):

537 """

538 Parameters allowed in the ``[export]`` section of the CamCOPS config file.

539 """

540

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"

550

551

552 class ConfigParamExportRecipient(object):

553 """

554 Possible configuration file parameters that relate to "export recipient"

555 definitions.

556 """

557

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"

617

618

619 class 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).

624

625 HMAC: Hash-based Message Authentication Code

626 https://en.wikipedia.org/wiki/HMAC

627

628 Values must be in lower case.

629 """

630

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

635

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)

642

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)

649

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

661

662

663 class SmsBackendNames:

664 """

665 Names of allowed SMS backends.

666 """

667

668 CONSOLE = "console"

669 KAPOW = "kapow"

670 TWILIO = "twilio"

671

672

673 class DockerConstants(object):

674 """

675 Constants for the Docker environment.

676 """

677

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")

683

684 DEFAULT_USER_DOWNLOAD_DIR = os.path.join(TMP_DIR, "user_downloads")

685 DEFAULT_LOCKDIR = os.path.join(TMP_DIR, "lock")

686

687 # Container (internal) names

688 CONTAINER_RABBITMQ = "rabbitmq"

689 CONTAINER_MYSQL = "mysql"

690

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

697

698

699# =============================================================================

700# Configuration defaults

701# =============================================================================

702

703

704 class ConfigDefaults(object):

705 """

706 Contains default values for the config, plus some cosmetic defaults for

707 generating specimen config files.

708

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 """

712

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

755

756 # Not yet user-configurable

757 PLOT_FONTSIZE = 8

758

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

769

770 if ENVVAR_GENERATING_CAMCOPS_DOCS in os.environ:

771 GUNICORN_NUM_WORKERS = 16

772 else:

773 GUNICORN_NUM_WORKERS = 2 * multiprocessing.cpu_count()

774

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

786

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"

794

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

821

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?

828

829 Defaults for use within Docker:

830

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

851

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 )

871

872

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.

890

891

892 class StringLengths:

893 # -------------------------------------------------------------------------

894 # Primary

895 # -------------------------------------------------------------------------

896 AUDIT_SOURCE_MAX_LEN = 20 #: our choice based on use in CamCOPS code

897

898 BASE32_MAX_LEN = 32

899

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

903

904 CURRENCY_MAX_LEN = 3 #: Can have Unicode symbols like € or text like "GBP"

905

906 DATABASE_TITLE_MIN_LEN = 1

907 DATABASE_TITLE_MAX_LEN = 255 #: our choice

908

909 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index;

910 #: must be compatible with tablet

911 DEVICE_NAME_MAX_LEN = 191

912

913 #: : See https://en.wikipedia.org/wiki/Email_address.

914 EMAIL_ADDRESS_MAX_LEN = 255

915

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

919

920 #: Our choice

921 FILTER_TEXT_MAX_LEN = 255

922

923 #: Our choice; used for user full names on the server

924 FULLNAME_MAX_LEN = 255

925

926 #: Our choice

927 FILESPEC_MAX_LEN = 255

928

929 GROUP_DESCRIPTION_MIN_LEN = 1

930 #: Our choice

931 GROUP_DESCRIPTION_MAX_LEN = 255

932

933 GROUP_NAME_MIN_LEN = 1

934 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index

935 GROUP_NAME_MAX_LEN = 191

936

937 HASHED_PW_MAX_LEN = 60

938 """

939 :

940 We use ``bcrypt``. Empirically, the length of its hashed output is:

941

942 .. code-block:: none

943

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)

948

949 ... total 60

950

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

953

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:

959

960 - component 2 is the Assigning Authority (see component 1)

961 - component 2 is also a Namespace ID with a length of 20

962

963 - ... and multiple other examples of an Assigning Authority being one

964 example of a Namespace ID

965

966 - ... and examples are in Table 0363 (p229 of the PDF), which are all

967 3-char.

968

969 - ... and several other examples of "Namespace ID" being of length 1..20

970 meaning 1-20.

971 """

972

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 """

978

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 """

984

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 """

990

991 #: longest is e.g. "F00.000"; "F10.202"; thus, 7

992 ICD10_CODE_MAX_LEN = 7

993

994 #: Our choice

995 ID_DESCRIPTOR_MAX_LEN = 255

996

997 #: Our choice

998 ID_POLICY_MAX_LEN = 255

999

1000 #: : See http://stackoverflow.com/questions/166132

1001 IP_ADDRESS_MAX_LEN = 45

1002

1003 ISO8601_DATETIME_STRING_MAX_LEN = 32

1004 """

1005 Max length e.g.

1006

1007 .. code-block:: none

1008

1009 2013-07-24T20:04:07.123456+01:00

1010 1234567890123456789012345678901234567890

1011

1012 (with punctuation, T, microseconds, colon in timezone).

1013 """

1014

1015 #: See :func:`cardinal_pythonlib.datetimefunc.duration_to_iso`

1016 ISO8601_DURATION_STRING_MAX_LEN = 29

1017

1018 #: Two-letter language, hyphen, 2/3-letter country

1019 LANGUAGE_CODE_MAX_LEN = 6

1020

1021 # LONGBLOB_LONGTEXT_MAX_LEN = (2 ** 32) - 1

1022 # ... https://dev.mysql.com/doc/refman/8.0/en/storage-requirements.html

1023

1024 # The longest is currently "hotp_email" (ViewArg, cc_pyramid.py)

1025 MFA_METHOD_MAX_LEN = 20

1026

1027 #: See https://stackoverflow.com/questions/643690

1028 MIMETYPE_MAX_LEN = 255

1029

1030 #: For forename and surname, each; our choice but must match tablet

1031 PATIENT_NAME_MAX_LEN = 255

1032

1033 PHONE_NUMBER_MAX_LEN = 128

1034

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 """

1040

1041 #: for export; our choice based on use in CamCOPS code

1042 SENDING_FORMAT_MAX_LEN = 50

1043

1044 #: our choice; 64 bytes => 512 bits, which is a lot in 2017

1045 SESSION_TOKEN_MAX_BYTES = 64

1046

1047 SQL_SEARCH_LITERAL_MIN_LENGTH = 0 # permits: LIKE ''

1048 SQL_SEARCH_LITERAL_MAX_LENGTH = 255 # arbitrary

1049

1050 TABLENAME_MAX_LEN = 128

1051 """

1052 For

1053

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

1058

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 """

1065

1066 #: Our choice

1067 URL_MAX_LEN = 255

1068

1069 USERNAME_CAMCOPS_MIN_LEN = 1

1070 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index

1071 USERNAME_CAMCOPS_MAX_LEN = 191

1072

1073 #: Our choice

1074 USERNAME_EXTERNAL_MAX_LEN = 255

1075

1076 # -------------------------------------------------------------------------

1077 # Derived

1078 # -------------------------------------------------------------------------

1079

1080 DIAGNOSTIC_CODE_MAX_LEN = max(ICD9_CODE_MAX_LEN, ICD10_CODE_MAX_LEN)

1081

1082 SESSION_TOKEN_MAX_LEN = len(

1083 create_base64encoded_randomness(SESSION_TOKEN_MAX_BYTES)

1084 )

1085

1086

1087# =============================================================================

1088# FHIR string constants

1089# =============================================================================

1090

1091

1092 class FHIRConst:

1093 """

1094 Constants used, mainly as dictionary keys, by the Python ``fhirclient``

1095 package.

1096

1097 - Capitalized: usually FHIR object types

1098 - lower_case or lowerCamelCase: usually dictionary keys

1099 - plainlowercase: often string constants

1100 """

1101

1102 # -------------------------------------------------------------------------

1103 # Authentication (FHIRClient settings)

1104 # -------------------------------------------------------------------------

1105

1106 API_BASE = "api_base"

1107 APP_ID = "app_id"

1108 APP_SECRET = "app_secret"

1109 LAUNCH_TOKEN = "launch_token"

1110

1111 # -------------------------------------------------------------------------

1112 # Generic keys (used by lots)

1113 # -------------------------------------------------------------------------

1114

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"

1127

1128 # -------------------------------------------------------------------------

1129 # Resource types (usually: BundleEntryRequest keys)

1130 # -------------------------------------------------------------------------

1131

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"

1140

1141 # -------------------------------------------------------------------------

1142 # Resource-specific keys

1143 # -------------------------------------------------------------------------

1144

1145 # Annotation keys

1146 AUTHOR_REFERENCE = "authorReference"

1147 AUTHOR_STRING = "authorString"

1148 TIME = "time"

1149

1150 # Attachment and Binary keys

1151 CONTENT_TYPE = "contentType"

1152 DATA = "data"

1153

1154 # Bundle keys

1155 TYPE = "type"

1156 ENTRY = "entry"

1157

1158 # BundleEntry keys

1159 REQUEST = "request"

1160 RESOURCE = "resource"

1161

1162 # BundleEntryRequest keys

1163 IF_NONE_EXIST = "ifNoneExist"

1164 METHOD = "method"

1165

1166 # CodeableConcept keys

1167 CODING = "coding"

1168 TEXT = "text"

1169

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"

1189

1190 # Condition keys

1191 NOTE = "note"

1192 RECORDER = "recorder" # = clinician

1193

1194 # ContactPoint values

1195 TELECOM_SYSTEM_EMAIL = "email"

1196 TELECOM_SYSTEM_OTHER = "other"

1197

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"

1207

1208 # DocumentReferenceContent keys:

1209 ATTACHMENT = "attachment"

1210 FORMAT = "format"

1211

1212 # HumanName keys

1213 NAME_FAMILY = "family"

1214 NAME_GIVEN = "given"

1215

1216 # Observation keys

1217 COMPONENT = "component"

1218 EFFECTIVE_DATE_TIME = "effectiveDateTime"

1219 # Observation values

1220 OBSSTATUS_FINAL = "final"

1221 OBSSTATUS_PRELIMINARY = "preliminary"

1222

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"

1238

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"

1249

1250 # Address keys

1251 ADDRESS_TEXT = "text"

1252

1253 # Quantity keys

1254 # COMPARATOR = "comparator"

1255 UNIT = "unit"

1256

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"

1265

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

1290

1291 # QuestionnaireResponse keys

1292 AUTHORED = "authored"

1293 QUESTIONNAIRE = "questionnaire"

1294

1295 # QuestionnaireResponseItem keys

1296 ANSWER = "answer"

1297

1298 # -------------------------------------------------------------------------

1299 # Very specific codes

1300 # -------------------------------------------------------------------------

1301

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"

1328

1329 # ID_SYSTEM_NHS_NUMBER = "https://fhir.nhs.uk/Id/nhs-number"

1330

1331 # -------------------------------------------------------------------------

1332 # Response values

1333 # -------------------------------------------------------------------------

1334

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"

1347

1348 # -------------------------------------------------------------------------

1349 # CamCOPS tags

1350 # -------------------------------------------------------------------------

1351

1352 CAMCOPS_VALUE_CLINICIAN_WITHIN_TASK = "clinician"

1353 CAMCOPS_VALUE_PATIENT_WITHIN_TASK = "patient"

1354 CAMCOPS_VALUE_QUESTIONNAIRE_RESPONSE_WITHIN_TASK = "qr"