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

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/cc_constants.py 

5 

6=============================================================================== 

7 

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

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

10 

11 This file is part of CamCOPS. 

12 

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. 

17 

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. 

22 

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

25 

26=============================================================================== 

27 

28**Various constants.** 

29 

30""" 

31 

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

33 

34import logging 

35import multiprocessing 

36import os 

37 

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 

42 

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 

51 

52 

53# ============================================================================= 

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

55# ============================================================================= 

56 

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. 

60 

61 

62# ============================================================================= 

63# File types 

64# ============================================================================= 

65 

66 

67class FileType(object): 

68 """ 

69 Used to represent output formats and their file extensions. 

70 """ 

71 

72 HTML = "html" 

73 PDF = "pdf" 

74 XML = "xml" 

75 

76 

77# ============================================================================= 

78# Encodings 

79# ============================================================================= 

80 

81ASCII = "ascii" 

82UTF8 = "utf8" 

83 

84 

85# ============================================================================= 

86# Launching 

87# ============================================================================= 

88 

89DEFAULT_FLOWER_ADDRESS = "127.0.0.1" 

90DEFAULT_FLOWER_PORT = ( 

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

92) 

93 

94 

95# ============================================================================= 

96# Webview constants 

97# ============================================================================= 

98 

99DEFAULT_ROWS_PER_PAGE = 25 

100DEVICE_NAME_FOR_SERVER = "server" # Do not alter. 

101USER_NAME_FOR_SYSTEM = "system" # Do not alter. 

102 

103# Address to download Windows/Mac versions: 

104GITHUB_RELEASES_URL = ( 

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

106) 

107 

108MINIMUM_PASSWORD_LENGTH = 10 

109 

110OBSCURE_PHONE_ASTERISKS = "*" * 10 

111OBSCURE_EMAIL_ASTERISKS = "*" * 5 

112 

113 

114# ============================================================================= 

115# Other display constants 

116# ============================================================================= 

117 

118JSON_INDENT = 4 

119 

120 

121# ============================================================================= 

122# Date formats 

123# ============================================================================= 

124 

125 

126class DateFormat(object): 

127 """ 

128 Assorted date/time formats. 

129 """ 

130 

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 

170 

171 

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

173# FHIR constants 

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

175 

176CAMCOPS_DEFAULT_FHIR_APP_ID = "camcops" 

177 

178 

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

180# Permitted values in fields: some common settings 

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

182 

183 

184class 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 

195NO_CHAR = "N" 

196YES_CHAR = "Y" 

197 

198# Database values: 

199SEX_FEMALE = "F" 

200SEX_MALE = "M" 

201SEX_OTHER_UNSPECIFIED = "X" 

202POSSIBLE_SEX_VALUES = (SEX_FEMALE, SEX_MALE, SEX_OTHER_UNSPECIFIED) 

203 

204 

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

206# Field names/specifications 

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

208 

209# Do not alter these! 

210TABLET_ID_FIELD = "id" 

211MOVE_OFF_TABLET_FIELD = "_move_off_tablet" 

212CLIENT_DATE_FIELD = "when_last_modified" 

213 

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

215FP_ID_NUM = "idnum" 

216FP_ID_DESC = "iddesc" 

217FP_ID_SHORT_DESC = "idshortdesc" 

218 

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

224 

225 

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

227# Other special values 

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

229 

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

231ERA_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 

239PDF_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 

249class 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 

272class 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 

301USE_SVG_IN_HTML = True # set to False for PNG debugging 

302 

303 

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

305# CSS/HTML constants 

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

307 

308CSS_PAGED_MEDIA = PDF_ENGINE != "pdfkit" 

309 

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} 

323 

324 

325class 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 

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

427 

428QUESTION = "Question" 

429 

430SPREADSHEET_PATIENT_FIELD_PREFIX = "_patient_" 

431 

432 

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

434# Config constants 

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

436 

437CONFIG_FILE_SITE_SECTION = "site" 

438CONFIG_FILE_SERVER_SECTION = "server" 

439CONFIG_FILE_EXPORT_SECTION = "export" 

440CONFIG_FILE_SMS_BACKEND_PREFIX = "sms_backend" 

441 

442 

443class 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 

495class 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 

536class 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 

552class 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 

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

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 

663class SmsBackendNames: 

664 """ 

665 Names of allowed SMS backends. 

666 """ 

667 

668 CONSOLE = "console" 

669 KAPOW = "kapow" 

670 TWILIO = "twilio" 

671 

672 

673class 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 

704class 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 = 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@@" 

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 port=self.DB_PORT, 

864 username=self.DB_USER, 

865 password=self.DB_PASSWORD, 

866 dbname=self.DB_DATABASE, 

867 ) 

868 

869 

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. 

887 

888 

889class StringLengths: 

890 # ------------------------------------------------------------------------- 

891 # Primary 

892 # ------------------------------------------------------------------------- 

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

894 

895 BASE32_MAX_LEN = 32 

896 

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 

900 

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

902 

903 DATABASE_TITLE_MIN_LEN = 1 

904 DATABASE_TITLE_MAX_LEN = 255 #: our choice 

905 

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

907 #: must be compatible with tablet 

908 DEVICE_NAME_MAX_LEN = 191 

909 

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

911 EMAIL_ADDRESS_MAX_LEN = 255 

912 

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 

916 

917 #: Our choice 

918 FILTER_TEXT_MAX_LEN = 255 

919 

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

921 FULLNAME_MAX_LEN = 255 

922 

923 #: Our choice 

924 FILESPEC_MAX_LEN = 255 

925 

926 GROUP_DESCRIPTION_MIN_LEN = 1 

927 #: Our choice 

928 GROUP_DESCRIPTION_MAX_LEN = 255 

929 

930 GROUP_NAME_MIN_LEN = 1 

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

932 GROUP_NAME_MAX_LEN = 191 

933 

934 HASHED_PW_MAX_LEN = 60 

935 """ 

936 : 

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

938 

939 .. code-block:: none 

940 

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) 

945 

946 ... total 60 

947 

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 

950 

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: 

956 

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

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

959 

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

961 example of a Namespace ID 

962 

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

964 3-char. 

965 

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

967 meaning 1-20. 

968 """ 

969 

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

975 

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 

981 

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 

987 

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

989 ICD10_CODE_MAX_LEN = 7 

990 

991 #: Our choice 

992 ID_DESCRIPTOR_MAX_LEN = 255 

993 

994 #: Our choice 

995 ID_POLICY_MAX_LEN = 255 

996 

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

998 IP_ADDRESS_MAX_LEN = 45 

999 

1000 ISO8601_DATETIME_STRING_MAX_LEN = 32 

1001 """ 

1002 Max length e.g. 

1003 

1004 .. code-block:: none 

1005 

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

1007 1234567890123456789012345678901234567890 

1008 

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

1010 """ 

1011 

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

1013 ISO8601_DURATION_STRING_MAX_LEN = 29 

1014 

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

1016 LANGUAGE_CODE_MAX_LEN = 6 

1017 

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

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

1020 

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

1022 MFA_METHOD_MAX_LEN = 20 

1023 

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

1025 MIMETYPE_MAX_LEN = 255 

1026 

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

1028 PATIENT_NAME_MAX_LEN = 255 

1029 

1030 PHONE_NUMBER_MAX_LEN = 128 

1031 

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

1037 

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

1039 SENDING_FORMAT_MAX_LEN = 50 

1040 

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

1042 SESSION_TOKEN_MAX_BYTES = 64 

1043 

1044 SQL_SEARCH_LITERAL_MIN_LENGTH = 0 # permits: LIKE '' 

1045 SQL_SEARCH_LITERAL_MAX_LENGTH = 255 # arbitrary 

1046 

1047 TABLENAME_MAX_LEN = 128 

1048 """ 

1049 For 

1050 

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 

1055 

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

1062 

1063 #: Our choice 

1064 URL_MAX_LEN = 255 

1065 

1066 USERNAME_CAMCOPS_MIN_LEN = 1 

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

1068 USERNAME_CAMCOPS_MAX_LEN = 191 

1069 

1070 #: Our choice 

1071 USERNAME_EXTERNAL_MAX_LEN = 255 

1072 

1073 # ------------------------------------------------------------------------- 

1074 # Derived 

1075 # ------------------------------------------------------------------------- 

1076 

1077 DIAGNOSTIC_CODE_MAX_LEN = max(ICD9_CODE_MAX_LEN, ICD10_CODE_MAX_LEN) 

1078 

1079 SESSION_TOKEN_MAX_LEN = len( 

1080 create_base64encoded_randomness(SESSION_TOKEN_MAX_BYTES) 

1081 ) 

1082 

1083 

1084# ============================================================================= 

1085# FHIR string constants 

1086# ============================================================================= 

1087 

1088 

1089class FHIRConst: 

1090 """ 

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

1092 package. 

1093 

1094 - Capitalized: usually FHIR object types 

1095 - lower_case or lowerCamelCase: usually dictionary keys 

1096 - plainlowercase: often string constants 

1097 """ 

1098 

1099 # ------------------------------------------------------------------------- 

1100 # Authentication (FHIRClient settings) 

1101 # ------------------------------------------------------------------------- 

1102 

1103 API_BASE = "api_base" 

1104 APP_ID = "app_id" 

1105 APP_SECRET = "app_secret" 

1106 LAUNCH_TOKEN = "launch_token" 

1107 

1108 # ------------------------------------------------------------------------- 

1109 # Generic keys (used by lots) 

1110 # ------------------------------------------------------------------------- 

1111 

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" 

1124 

1125 # ------------------------------------------------------------------------- 

1126 # Resource types (usually: BundleEntryRequest keys) 

1127 # ------------------------------------------------------------------------- 

1128 

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" 

1137 

1138 # ------------------------------------------------------------------------- 

1139 # Resource-specific keys 

1140 # ------------------------------------------------------------------------- 

1141 

1142 # Annotation keys 

1143 AUTHOR_REFERENCE = "authorReference" 

1144 AUTHOR_STRING = "authorString" 

1145 TIME = "time" 

1146 

1147 # Attachment and Binary keys 

1148 CONTENT_TYPE = "contentType" 

1149 DATA = "data" 

1150 

1151 # Bundle keys 

1152 TYPE = "type" 

1153 ENTRY = "entry" 

1154 

1155 # BundleEntry keys 

1156 REQUEST = "request" 

1157 RESOURCE = "resource" 

1158 

1159 # BundleEntryRequest keys 

1160 IF_NONE_EXIST = "ifNoneExist" 

1161 METHOD = "method" 

1162 

1163 # CodeableConcept keys 

1164 CODING = "coding" 

1165 TEXT = "text" 

1166 

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" 

1186 

1187 # Condition keys 

1188 NOTE = "note" 

1189 RECORDER = "recorder" # = clinician 

1190 

1191 # ContactPoint values 

1192 TELECOM_SYSTEM_EMAIL = "email" 

1193 TELECOM_SYSTEM_OTHER = "other" 

1194 

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" 

1204 

1205 # DocumentReferenceContent keys: 

1206 ATTACHMENT = "attachment" 

1207 FORMAT = "format" 

1208 

1209 # HumanName keys 

1210 NAME_FAMILY = "family" 

1211 NAME_GIVEN = "given" 

1212 

1213 # Observation keys 

1214 COMPONENT = "component" 

1215 EFFECTIVE_DATE_TIME = "effectiveDateTime" 

1216 # Observation values 

1217 OBSSTATUS_FINAL = "final" 

1218 OBSSTATUS_PRELIMINARY = "preliminary" 

1219 

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" 

1235 

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" 

1246 

1247 # Address keys 

1248 ADDRESS_TEXT = "text" 

1249 

1250 # Quantity keys 

1251 # COMPARATOR = "comparator" 

1252 UNIT = "unit" 

1253 

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" 

1262 

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 

1287 

1288 # QuestionnaireResponse keys 

1289 AUTHORED = "authored" 

1290 QUESTIONNAIRE = "questionnaire" 

1291 

1292 # QuestionnaireResponseItem keys 

1293 ANSWER = "answer" 

1294 

1295 # ------------------------------------------------------------------------- 

1296 # Very specific codes 

1297 # ------------------------------------------------------------------------- 

1298 

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" 

1325 

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

1327 

1328 # ------------------------------------------------------------------------- 

1329 # Response values 

1330 # ------------------------------------------------------------------------- 

1331 

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" 

1344 

1345 # ------------------------------------------------------------------------- 

1346 # CamCOPS tags 

1347 # ------------------------------------------------------------------------- 

1348 

1349 CAMCOPS_VALUE_CLINICIAN_WITHIN_TASK = "clinician" 

1350 CAMCOPS_VALUE_PATIENT_WITHIN_TASK = "patient" 

1351 CAMCOPS_VALUE_QUESTIONNAIRE_RESPONSE_WITHIN_TASK = "qr"