Coverage for cc_modules/cc_constants.py: 97%

662 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 14:23 +0100

1""" 

2camcops_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 

32import logging 

33import multiprocessing 

34import os 

35from typing import cast 

36 

37from cardinal_pythonlib.randomness import create_base64encoded_randomness 

38from cardinal_pythonlib.sqlalchemy.session import make_mysql_url 

39from cardinal_pythonlib.tcpipconst import Ports 

40from cardinal_pythonlib.uriconst import UriSchemes 

41 

42from camcops_server.cc_modules.cc_baseconstants import ( 

43 DEFAULT_EXTRA_STRINGS_DIR, 

44 ENVVAR_GENERATING_CAMCOPS_DOCS, 

45 LINUX_DEFAULT_LOCK_DIR, 

46 LINUX_DEFAULT_USER_DOWNLOAD_DIR, 

47 STATIC_ROOT_DIR, 

48) 

49from camcops_server.cc_modules.cc_language import DEFAULT_LOCALE 

50 

51 

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

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

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

55 

56NUMBER_OF_IDNUMS_DEFUNCT = 8 # DEFUNCT BUT DO NOT REMOVE OR ALTER. EIGHT. 

57# ... In older versions: determined number of ID number fields. 

58# (Now this is arbitrary.) Still used to support old clients. 

59 

60 

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

62# File types 

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

64 

65 

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

80ASCII = "ascii" 

81UTF8 = "utf8" 

82 

83 

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

85# Launching 

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

87 

88DEFAULT_FLOWER_ADDRESS = "127.0.0.1" 

89DEFAULT_FLOWER_PORT = ( 

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

91) 

92 

93 

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

95# Webview constants 

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

97 

98DEFAULT_ROWS_PER_PAGE = 25 

99DEVICE_NAME_FOR_SERVER = "server" # Do not alter. 

100USER_NAME_FOR_SYSTEM = "system" # Do not alter. 

101 

102# Address to download Windows/Mac versions: 

103GITHUB_RELEASES_URL = ( 

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

105) 

106 

107MINIMUM_PASSWORD_LENGTH = 10 

108 

109OBSCURE_PHONE_ASTERISKS = "*" * 10 

110OBSCURE_EMAIL_ASTERISKS = "*" * 5 

111 

112 

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

114# Other display constants 

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

116 

117JSON_INDENT = 4 

118 

119 

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

121# Date formats 

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

123 

124 

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

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

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

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