Package Gnumed :: Package wxpython :: Module gmMacro
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmMacro

   1  # -*- coding: utf-8 -*- 
   2  """GNUmed macro primitives. 
   3   
   4  This module implements functions a macro can legally use. 
   5  """ 
   6  #===================================================================== 
   7  __author__ = "K.Hilbert <karsten.hilbert@gmx.net>" 
   8   
   9  import sys 
  10  import time 
  11  import random 
  12  import types 
  13  import logging 
  14  import os 
  15  import io 
  16  import datetime 
  17  import urllib.parse 
  18  import codecs 
  19  import re as regex 
  20   
  21   
  22  import wx 
  23   
  24   
  25  if __name__ == '__main__': 
  26          sys.path.insert(0, '../../') 
  27  from Gnumed.pycommon import gmI18N 
  28  if __name__ == '__main__': 
  29          gmI18N.activate_locale() 
  30          gmI18N.install_domain() 
  31  from Gnumed.pycommon import gmGuiBroker 
  32  from Gnumed.pycommon import gmTools 
  33  from Gnumed.pycommon import gmBorg 
  34  from Gnumed.pycommon import gmExceptions 
  35  from Gnumed.pycommon import gmCfg2 
  36  from Gnumed.pycommon import gmDateTime 
  37  from Gnumed.pycommon import gmMimeLib 
  38  from Gnumed.pycommon import gmShellAPI 
  39  from Gnumed.pycommon import gmCrypto 
  40   
  41  from Gnumed.business import gmPerson 
  42  from Gnumed.business import gmStaff 
  43  from Gnumed.business import gmDemographicRecord 
  44  from Gnumed.business import gmMedication 
  45  from Gnumed.business import gmPathLab 
  46  from Gnumed.business import gmPersonSearch 
  47  from Gnumed.business import gmVaccination 
  48  from Gnumed.business import gmKeywordExpansion 
  49  from Gnumed.business import gmPraxis 
  50   
  51  from Gnumed.wxpython import gmGuiHelpers 
  52  from Gnumed.wxpython import gmNarrativeWorkflows 
  53  from Gnumed.wxpython import gmPatSearchWidgets 
  54  from Gnumed.wxpython import gmPersonContactWidgets 
  55  from Gnumed.wxpython import gmPlugin 
  56  from Gnumed.wxpython import gmEMRStructWidgets 
  57  from Gnumed.wxpython import gmEncounterWidgets 
  58  from Gnumed.wxpython import gmListWidgets 
  59  from Gnumed.wxpython import gmDemographicsWidgets 
  60  from Gnumed.wxpython import gmDocumentWidgets 
  61  from Gnumed.wxpython import gmKeywordExpansionWidgets 
  62  from Gnumed.wxpython import gmPraxisWidgets 
  63  from Gnumed.wxpython import gmAddressWidgets 
  64   
  65   
  66  _log = logging.getLogger('gm.scripting') 
  67  _cfg = gmCfg2.gmCfgData() 
  68   
  69  #===================================================================== 
  70  # values for the following placeholders must be injected from the outside before 
  71  # using them, in use they must conform to the "placeholder::::max length" syntax, 
  72  # as long as they resolve to None they return their respective names so the 
  73  # developers can know which placeholder was not set 
  74  known_injectable_placeholders = [ 
  75          'form_name_long', 
  76          'form_name_short', 
  77          'form_version', 
  78          'form_version_internal', 
  79          'form_last_modified' 
  80  ] 
  81   
  82  # the following must satisfy the pattern "$<name::args::(optional) max string length>$" when used 
  83  __known_variant_placeholders = { 
  84          # generic: 
  85          'free_text': u"""show a dialog for entering some free text: 
  86                  args: <message>//<preset> 
  87                          <message>: shown in input dialog, must not contain either 
  88                                  of '::' and whatever the arguments divider is 
  89                                  set to (default '//'), 
  90                          <preset>: whatever to initially show inside the input field, 
  91                  caches input per <message>""", 
  92   
  93          'text_snippet': """a text snippet, taken from the keyword expansion mechanism: 
  94                  args: <snippet name>//<template>""", 
  95   
  96          'data_snippet': """a binary snippet, taken from the keyword expansion mechanism: 
  97                  args: <snippet name>//<template>//<optional target mime type>//<optional target extension> 
  98                  returns full path to an exported copy of the 
  99                  data rather than the data itself, 
 100                  template: string template for outputting the path 
 101                  target mime type: a mime type into which to convert the image, no conversion if not given 
 102                  target extension: target file name extension, derived from target mime type if not given 
 103          """, 
 104          u'qrcode': u"""generate QR code file for a text snippet: 
 105                  returns: path to QR code png file 
 106                  args: <text>//<template> 
 107                          text: utf8 text, will always be encoded in 'binary' mode with utf8 as encoding 
 108                          template: %s-template into which to insert the QR code png file path 
 109          """, 
 110   
 111          # text manipulation 
 112          'range_of': """select range of enclosed text (note that this cannot take into account non-length characters such as enclosed LaTeX code 
 113                  args: <enclosed text> 
 114          """, 
 115          'if_not_empty': """format text based on template if not empty 
 116                  args: <possibly-empty-text>//<template-if-not-empty>//<alternative-text-if-empty> 
 117          """, 
 118   
 119          u'tex_escape': u"args: string to escape, mostly obsolete now", 
 120   
 121          u'url_escape': u"""Escapes a string suitable for use as _data_ in an URL 
 122                  args: text to escape 
 123          """, 
 124   
 125          # internal state 
 126          'ph_cfg': u"""Set placeholder handler options. 
 127                  args: option name//option value//macro return string 
 128                  option names: 
 129                          ellipsis: what to use as ellipsis (if anything) when 
 130                                  shortening strings or string regions, setting the 
 131                                  value to NONE will switch off ellipis handling, 
 132                                  default is switched off 
 133                          argumentsdivider: what to use as divider when splitting 
 134                                  an argument string into parts, default is '//', 
 135                                  note that the 'config' placeholder will ALWAYS 
 136                                  use '//' to split its argument string, regardless 
 137                                  of which setting of <argumentsdivider> is in effect, 
 138                                  use DEFAULT to reset this setting back to the 
 139                                  default '//' 
 140                          encoding: the encoding in which data emitted by GNUmed 
 141                                  as placeholder replacement needs to be valid in, 
 142                                  note that GNUmed will still emit unicode to replacement 
 143                                  consumers but it will ensure the data emitted _can_ 
 144                                  be encoded by this target encoding (by roundtripping 
 145                                  unicode-encoding-unicode) 
 146                                  valid from where this placeholder is located at 
 147                                  until further change, 
 148                                  use DEFAULT to reset encoding back to the default 
 149                                  which is to not ensure compatibility, 
 150                                  if the encoding ends in '-strict' then the placeholder 
 151                                  replacement will fail if the roundtrip fails 
 152          """, 
 153          u'if_debugging': u"""set text based on whether debugging is active 
 154                  args: <text-if-debugging>//<text-if-not-debugging> 
 155          """, 
 156   
 157          'today': "args: strftime format", 
 158   
 159          'gender_mapper': """maps gender of patient to a string: 
 160                  args: <value when person is male> // <is female> // <is other> 
 161                  eg. 'male//female//other' 
 162                  or: 'Lieber Patient//Liebe Patientin'""", 
 163          'client_version': "the version of the current client as a string (no 'v' in front)", 
 164   
 165          'gen_adr_street': """part of a generic address, cached, selected from database: 
 166                  args: optional template//optional selection message//optional cache ID 
 167                  template: %s-style formatting template 
 168                  message: text message shown in address selection list 
 169                  cache ID: used to differentiate separate cached invocations of this placeholder 
 170          """, 
 171          'gen_adr_number': """part of a generic address, cached, selected from database: 
 172                  args: optional template//optional selection message//optional cache ID 
 173                  template: %s-style formatting template 
 174                  message: text message shown in address selection list 
 175                  cache ID: used to differentiate separate cached invocations of this placeholder 
 176          """, 
 177          'gen_adr_subunit': """part of a generic address, cached, selected from database: 
 178                  args: optional template//optional selection message//optional cache ID 
 179                  template: %s-style formatting template 
 180                  message: text message shown in address selection list 
 181                  cache ID: used to differentiate separate cached invocations of this placeholder 
 182          """, 
 183          'gen_adr_location': """part of a generic address, cached, selected from database: 
 184                  args: optional template//optional selection message//optional cache ID 
 185                  template: %s-style formatting template 
 186                  message: text message shown in address selection list 
 187                  cache ID: used to differentiate separate cached invocations of this placeholder 
 188          """, 
 189          'gen_adr_suburb': """part of a generic address, cached, selected from database: 
 190                  args: optional template//optional selection message//optional cache ID 
 191                  template: %s-style formatting template 
 192                  message: text message shown in address selection list 
 193                  cache ID: used to differentiate separate cached invocations of this placeholder 
 194          """, 
 195          'gen_adr_postcode': """part of a generic address, cached, selected from database: 
 196                  args: optional template//optional selection message//optional cache ID 
 197                  template: %s-style formatting template 
 198                  message: text message shown in address selection list 
 199                  cache ID: used to differentiate separate cached invocations of this placeholder 
 200          """, 
 201          'gen_adr_region': """part of a generic address, cached, selected from database: 
 202                  args: optional template//optional selection message//optional cache ID 
 203                  template: %s-style formatting template 
 204                  message: text message shown in address selection list 
 205                  cache ID: used to differentiate separate cached invocations of this placeholder 
 206          """, 
 207          'gen_adr_country': """part of a generic address, cached, selected from database: 
 208                  args: optional template//optional selection message//optional cache ID 
 209                  template: %s-style formatting template 
 210                  message: text message shown in address selection list 
 211                  cache ID: used to differentiate separate cached invocations of this placeholder 
 212          """, 
 213   
 214          'receiver_name': """the receiver name, cached, selected from database: 
 215                  receivers are presented for selection from people/addresses related 
 216                  to the patient in some way or other, 
 217                  args: optional template//optional cache ID 
 218                  template: %s-style formatting template 
 219                  cache ID: used to differentiate separate cached invocations of this placeholder 
 220          """, 
 221          'receiver_street': """part of a receiver address, cached, selected from database: 
 222                  receivers are presented for selection from people/addresses related 
 223                  to the patient in some way or other, 
 224                  args: optional template//optional cache ID 
 225                  template: %s-style formatting template 
 226                  cache ID: used to differentiate separate cached invocations of this placeholder 
 227          """, 
 228          'receiver_number': """part of a receiver address, cached, selected from database: 
 229                  receivers are presented for selection from people/addresses related 
 230                  to the patient in some way or other, 
 231                  args: optional template//optional cache ID 
 232                  template: %s-style formatting template 
 233                  cache ID: used to differentiate separate cached invocations of this placeholder 
 234          """, 
 235          'receiver_subunit': """part of a receiver address, cached, selected from database: 
 236                  receivers are presented for selection from people/addresses related 
 237                  to the patient in some way or other, 
 238                  args: optional template//optional cache ID 
 239                  template: %s-style formatting template 
 240                  cache ID: used to differentiate separate cached invocations of this placeholder 
 241          """, 
 242          'receiver_location': """part of a receiver address, cached, selected from database: 
 243                  receivers are presented for selection from people/addresses related 
 244                  to the patient in some way or other, 
 245                  args: optional template//optional cache ID 
 246                  template: %s-style formatting template 
 247                  cache ID: used to differentiate separate cached invocations of this placeholder 
 248          """, 
 249          'receiver_suburb': """part of a receiver address, cached, selected from database: 
 250                  receivers are presented for selection from people/addresses related 
 251                  to the patient in some way or other, 
 252                  args: optional template//optional cache ID 
 253                  template: %s-style formatting template 
 254                  cache ID: used to differentiate separate cached invocations of this placeholder 
 255          """, 
 256          'receiver_postcode': """part of a receiver address, cached, selected from database: 
 257                  receivers are presented for selection from people/addresses related 
 258                  to the patient in some way or other, 
 259                  args: optional template//optional cache ID 
 260                  template: %s-style formatting template 
 261                  cache ID: used to differentiate separate cached invocations of this placeholder 
 262          """, 
 263          'receiver_region': """part of a receiver address, cached, selected from database: 
 264                  receivers are presented for selection from people/addresses related 
 265                  to the patient in some way or other, 
 266                  args: optional template//optional cache ID 
 267                  template: %s-style formatting template 
 268                  cache ID: used to differentiate separate cached invocations of this placeholder 
 269          """, 
 270          'receiver_country': """part of a receiver address, cached, selected from database: 
 271                  receivers are presented for selection from people/addresses related 
 272                  to the patient in some way or other, 
 273                  args: optional template//optional cache ID 
 274                  template: %s-style formatting template 
 275                  cache ID: used to differentiate separate cached invocations of this placeholder 
 276          """, 
 277   
 278          # patient demographics: 
 279          'name': "args: template for name parts arrangement", 
 280          'date_of_birth': "args: strftime date/time format directive", 
 281   
 282          'patient_address': "args: <type of address>//<optional formatting template>", 
 283          'adr_street': "args: <type of address>, cached per type", 
 284          'adr_number': "args: <type of address>, cached per type", 
 285          'adr_subunit': "args: <type of address>, cached per type", 
 286          'adr_location': "args: <type of address>, cached per type", 
 287          'adr_suburb': "args: <type of address>, cached per type", 
 288          'adr_postcode': "args: <type of address>, cached per type", 
 289          'adr_region': "args: <type of address>, cached per type", 
 290          'adr_country': "args: <type of address>, cached per type", 
 291   
 292          'patient_comm': "args: <comm channel type as per database>//<%(field)s-template>", 
 293   
 294          'patient_vcf': """returns path to VCF for current patient 
 295                  args: <template> 
 296                  template: %s-template for path 
 297          """, 
 298          'patient_gdt': """returns path to GDT for current patient 
 299                  args: <template> 
 300                  template: %s-template for path 
 301          """, 
 302          u'patient_mcf': u"""returns MECARD for current patient 
 303                  args: <format>//<template> 
 304                  format: fmt=qr|mcf|txt 
 305                          qr: QR code png file path, 
 306                          mcf: MECARD .mcf file path, 
 307                          txt: MECARD string, 
 308                          default - if omitted - is "txt", 
 309                  template: tmpl=<%s-template string>, "%s" if omitted 
 310          """, 
 311   
 312          'patient_tags': "args: <%(field)s-template>//<separator>", 
 313          #u'patient_tags_table': u"no args", 
 314          'patient_photo': """outputs URL to exported patient photo (cached per mime type and extension): 
 315                  args: <template>//<optional target mime type>//<optional target extension>, 
 316                  returns full path to an exported copy of the 
 317                  image rather than the image data itself, 
 318                  returns u'' if no mugshot available, 
 319                  template: string template for outputting the path 
 320                  target mime type: a mime type into which to convert the image, no conversion if not given 
 321                  target extension: target file name extension, derived from target mime type if not given""", 
 322          'external_id': "args: <type of ID>//<issuer of ID>", 
 323   
 324   
 325          # clinical record related: 
 326          'soap': "get all of SOAPU/ADMIN, no template in args needed", 
 327          'soap_s': "get subset of SOAPU/ADMIN, no template in args needed", 
 328          'soap_o': "get subset of SOAPU/ADMIN, no template in args needed", 
 329          'soap_a': "get subset of SOAPU/ADMIN, no template in args needed", 
 330          'soap_p': "get subset of SOAPU/ADMIN, no template in args needed", 
 331          'soap_u': "get subset of SOAPU/ADMIN, no template in args needed", 
 332          'soap_admin': "get subset of SOAPU/ADMIN, no template in args needed", 
 333   
 334          'progress_notes': """get progress notes: 
 335                  args: categories//template 
 336                  categories: string with 'soapu '; ' ' == None == admin 
 337                  template:       u'something %s something'               (do not include // in template !)""", 
 338   
 339          'soap_for_encounters': """lets the user select a list of encounters for which: 
 340                  LaTeX formatted progress notes are emitted, 
 341                  args: soap categories // strftime date format""", 
 342   
 343          'soap_by_issue': """lets the user select a list of issues and then SOAP entries from those issues: 
 344                  args: soap categories // strftime date format // template""", 
 345   
 346          'soap_by_episode': """lets the user select a list of episodes and then SOAP entries from those episodes: 
 347                  args: soap categories // strftime date format // template""", 
 348   
 349          'emr_journal': """returns EMR journal view entries: 
 350                  args format:   <categories>//<template>//<line length>//<time range>//<target format> 
 351                  categories:        string with any of "s", "o", "a", "p", "u", " "; (" " == None == admin category) 
 352                  template:          something %s something else (Do not include // in the template !) 
 353                  line length:   the maximum length of individual lines, not the total placeholder length 
 354                  time range:             the number of weeks going back in time if given as a single number, or else it must be a valid PostgreSQL interval definition (w/o the ::interval)""", 
 355   
 356          'substance_abuse': """returns substance abuse entries: 
 357                  args: line template 
 358          """, 
 359   
 360          'current_meds': """returns current medications: 
 361                  args: line template//<select> 
 362                  <select>: if this is present the user will be asked which meds to export""", 
 363   
 364          'current_meds_for_rx': """formats substance intakes either by substance (non-product intakes) or by producdt (once per product intake, even if multi-component): 
 365                  args: <line template> 
 366                  <line_template>: template into which to insert each intake, keys from 
 367                  clin.v_substance_intakes, special additional keys: 
 368                          %(contains)s -- list of components 
 369                          %(amount2dispense)s -- how much/many to dispense""", 
 370   
 371          'current_meds_AMTS': """emit LaTeX longtable lines with appropriate page breaks: 
 372                  also creates per-page AMTS QR codes and sets the 
 373                  following internal placeholders: 
 374                          amts_png_file_1 
 375                          amts_png_file_2 
 376                          amts_png_file_3 
 377                          amts_data_file_1 
 378                          amts_data_file_2 
 379                          amts_data_file_3 
 380                          amts_png_file_current_page 
 381                          amts_data_file_utf8 
 382                          amts_png_file_utf8 
 383                  the last of which contains the LaTeX command \\thepage (such that 
 384                  LaTeX can use this in, say, page headers) but omitting the .png 
 385                  (for which LaTeX will look by itself), 
 386                  note that you will have to use the 2nd- or 3rd-pass placeholder 
 387                  format if you plan to insert the above because they will only be 
 388                  available by first (or second) pass processing of the initial 
 389                  placeholder "current_meds_AMTS" 
 390          """, 
 391          'current_meds_AMTS_enhanced': """emit LaTeX longtable lines with appropriate page breaks: 
 392                  this returns the same content as current_meds_AMTS except that 
 393                  it does not truncate output data whenever possible 
 394          """, 
 395   
 396          'current_meds_table': "emits a LaTeX table, no arguments", 
 397          'current_meds_notes': "emits a LaTeX table, no arguments", 
 398          'lab_table': "emits a LaTeX table, no arguments", 
 399          'test_results': "args: <%(field)s-template>//<date format>//<line separator (EOL)>", 
 400          'latest_vaccs_table': "emits a LaTeX table, no arguments", 
 401          'vaccination_history': "args: <%(field)s-template//date format> to format one vaccination per line", 
 402          'allergy_state': "no arguments", 
 403          'allergies': "args: line template, one allergy per line", 
 404          'allergy_list': "args holds: template per allergy, all allergies on one line", 
 405          'problems': "args holds: line template, one problem per line", 
 406          'diagnoses': 'args: line template, one diagnosis per line', 
 407          'PHX': "Past medical HiXtory; args: line template//separator//strftime date format", 
 408          'encounter_list': "args: per-encounter template, each ends up on one line", 
 409   
 410          'documents': """retrieves documents from the archive: 
 411                  args:   <select>//<description>//<template>//<path template>//<path> 
 412                  select: let user select which documents to include, optional, if not given: all documents included 
 413                  description:    whether to include descriptions, optional 
 414                  template:       something %(field)s something else (do not include '//' or '::' itself in the template) 
 415                  path template:  the template for outputting the path to exported 
 416                          copies of the document pages, if not given no pages are exported, 
 417                          this template can contain "%(name)s" and/or "%(fullpath)s" which  
 418                          is replaced by the appropriate value for each exported file 
 419                  path:   into which path to export copies of the document pages, temp dir if not given""", 
 420   
 421          'reminders': """patient reminders: 
 422                  args:   <template>//<date format> 
 423                  template:       something %(field)s something else (do not include '//' or '::' itself in the template)""", 
 424   
 425          'external_care': """External care entries: 
 426                  args:   <template> 
 427                  template:       something %(field)s something else (do not include '//' or '::' itself in the template)""", 
 428   
 429          # provider related: 
 430          'current_provider': "no arguments", 
 431          'current_provider_name': """formatted name of current provider: 
 432                  args: <template>, 
 433                  template:       something %(field)s something else (do not include '//' or '::' itself in the template) 
 434          """, 
 435          'current_provider_title': """formatted name of current provider: 
 436                  args: <optional template>, 
 437                  template:       something %(title)s something else (do not include '//' or '::' itself in the template) 
 438          """, 
 439          'current_provider_firstnames': """formatted name of current provider: 
 440                  args: <optional template>, 
 441                  template:       something %(firstnames)s something else (do not include '//' or '::' itself in the template) 
 442          """, 
 443          'current_provider_lastnames': """formatted name of current provider: 
 444                  args: <optional template>, 
 445                  template:       something %(lastnames)s something else (do not include '//' or '::' itself in the template) 
 446          """, 
 447          'current_provider_external_id': "args: <type of ID>//<issuer of ID>", 
 448          'primary_praxis_provider': "primary provider for current patient in this praxis", 
 449          'primary_praxis_provider_external_id': "args: <type of ID>//<issuer of ID>", 
 450   
 451   
 452          # praxis related: 
 453          'praxis': """retrieve current branch of your praxis: 
 454                  args: <template>//select 
 455                  template:               something %(field)s something else (do not include '//' or '::' itself in the template) 
 456                  select:                 if this is present allow selection of the branch rather than using the current branch""", 
 457   
 458          'praxis_address': "args: <optional formatting template>", 
 459          'praxis_comm': "args: type//<optional formatting template>", 
 460          'praxis_id': "args: <type of ID>//<issuer of ID>//<optional formatting template>", 
 461          'praxis_vcf': """returns path to VCF for current praxis branch 
 462                  args: <template> 
 463                  template: %s-template for path 
 464          """, 
 465          u'praxis_mcf': u"""returns MECARD for current praxis branch 
 466                  args: <format>//<template> 
 467                  format: fmt=qr|mcf|txt 
 468                          qr: QR code png file path, 
 469                          mcf: MECARD .mcf file path, 
 470                          txt: MECARD string, 
 471                          default - if omitted - is "txt", 
 472                  template: tmpl=<%s-template string>, "%s" if omitted 
 473          """, 
 474   
 475          # billing related: 
 476          'bill': """retrieve a bill 
 477                  args: <template>//<date format> 
 478                  template:               something %(field)s something else (do not include '//' or '::' itself in the template) 
 479                  date format:    strftime date format""", 
 480          'bill_item': """retrieve the items of a previously retrieved (and therefore cached until the next retrieval) bill 
 481                  args: <template>//<date format> 
 482                  template:               something %(field)s something else (do not include '//' or '::' itself in the template) 
 483                  date format:    strftime date format""", 
 484          'bill_adr_street': "args: optional template (%s-style formatting template); cached per bill", 
 485          'bill_adr_number': "args: optional template (%s-style formatting template); cached per bill", 
 486          'bill_adr_subunit': "args: optional template (%s-style formatting template); cached per bill", 
 487          'bill_adr_location': "args: optional template (%s-style formatting template); cached per bill", 
 488          'bill_adr_suburb': "args: optional template (%s-style formatting template); cached per bill", 
 489          'bill_adr_postcode': "args: optional template (%s-style formatting template); cached per bill", 
 490          'bill_adr_region': "args: optional template (%s-style formatting template); cached per bill", 
 491          'bill_adr_country': "args: optional template (%s-style formatting template); cached per bill" 
 492   
 493  } 
 494   
 495  known_variant_placeholders = __known_variant_placeholders.keys() 
 496  #known_variant_placeholders.sort() 
 497   
 498   
 499  # http://help.libreoffice.org/Common/List_of_Regular_Expressions 
 500  # except that OOo cannot be non-greedy |-( 
 501  #default_placeholder_regex = r'\$<.+?>\$'                               # previous working placeholder 
 502          # regex logic: 
 503          # starts with "$" 
 504          # followed by "<" 
 505          # followed by > 0 characters but NOT "<" but ONLY up to the NEXT ":" 
 506          # followed by "::" 
 507          # followed by any number of characters  but ONLY up to the NEXT ":" 
 508          # followed by "::" 
 509          # followed by any number of numbers 
 510          # followed by ">" 
 511          # followed by "$" 
 512   
 513  # previous: 
 514  default_placeholder_regex = r'\$<[^<:]+::.*?::\d*?>\$|\$<[^<:]+::.*?::\d+-\d+>\$'         # this one works [except that OOo cannot be non-greedy |-(    ] 
 515  first_pass_placeholder_regex = r'|'.join ([ 
 516          r'\$<[^<:]+::.*?(?=::\d*?>\$)::\d*?>\$', 
 517          r'\$<[^<:]+::.*?(?=::\d+-\d+>\$)::\d+-\d+>\$' 
 518  ]) 
 519  second_pass_placeholder_regex = r'|'.join ([ 
 520          r'\$<<[^<:]+?::.*?(?=::\d*?>>\$)::\d*?>>\$', 
 521          r'\$<<[^<:]+?::.*?(?=::\d+-\d+>>\$)::\d+-\d+>>\$' 
 522  ]) 
 523  third_pass_placeholder_regex = r'|'.join ([ 
 524          r'\$<<<[^<:]+?::.*?(?=::\d*?>>>\$)::\d*?>>>\$', 
 525          r'\$<<<[^<:]+?::.*?(?=::\d+-\d+>>>\$)::\d+-\d+>>>\$' 
 526  ]) 
 527   
 528  default_placeholder_start = '$<' 
 529  default_placeholder_end = '>$' 
 530   
 531  #===================================================================== 
532 -def show_placeholders():
533 534 fname = gmTools.get_unique_filename(prefix = 'gm-placeholders-', suffix = '.txt') 535 ph_file = io.open(fname, mode = 'wt', encoding = 'utf8', errors = 'replace') 536 537 ph_file.write('Here you can find some more documentation on placeholder use:\n') 538 ph_file.write('\n http://wiki.gnumed.de/bin/view/Gnumed/GmManualLettersForms\n\n\n') 539 540 ph_file.write('Variable placeholders:\n') 541 ph_file.write('Usage: $<PLACEHOLDER_NAME::ARGUMENTS::REGION_DEFINITION>$)\n') 542 ph_file.write(' REGION_DEFINITION:\n') 543 ph_file.write('* a single number specifying the maximum output length or\n') 544 ph_file.write('* a number, a "-", followed by a second number specifying the region of the string to return\n') 545 ph_file.write('ARGUMENTS:\n') 546 ph_file.write('* depend on the actual placeholder (see there)\n') 547 ph_file.write('* if a template is supported it will be used to %-format the output\n') 548 ph_file.write('* templates may be either %s-style or %(name)s-style\n') 549 ph_file.write('* templates cannot contain "::"\n') 550 ph_file.write('* templates cannot contain whatever the arguments divider is set to (default "//")\n') 551 for ph in known_variant_placeholders: 552 txt = __known_variant_placeholders[ph] 553 ph_file.write('\n') 554 ph_file.write(' ---=== %s ===---\n' % ph) 555 ph_file.write('\n') 556 ph_file.write(txt) 557 ph_file.write('\n\n') 558 ph_file.write('\n') 559 560 ph_file.write('Known injectable placeholders (use like: $<PLACEHOLDER_NAME::ARGUMENTS::MAX OUTPUT LENGTH>$):\n') 561 for ph in known_injectable_placeholders: 562 ph_file.write(' %s\n' % ph) 563 ph_file.write('\n') 564 565 ph_file.close() 566 gmMimeLib.call_viewer_on_file(aFile = fname, block = False)
567 568 #=====================================================================
569 -class gmPlaceholderHandler(gmBorg.cBorg):
570 """Returns values for placeholders. 571 572 - patient related placeholders operate on the currently active patient 573 - is passed to the forms handling code, for example 574 575 Return values when .debug is False: 576 - errors with placeholders return None 577 - placeholders failing to resolve to a value return an empty string 578 579 Return values when .debug is True: 580 - errors with placeholders return an error string 581 - placeholders failing to resolve to a value return a warning string 582 583 There are several types of placeholders: 584 585 injectable placeholders 586 - they must be set up before use by set_placeholder() 587 - they should be removed after use by unset_placeholder() 588 - the syntax is like extended static placeholders 589 - known ones are listed in known_injectable_placeholders 590 - per-form ones can be used but must exist before 591 the form is processed 592 593 variant placeholders 594 - those are listed in known_variant_placeholders 595 - they are parsed into placeholder, data, and maximum length 596 - the length is optional 597 - data is passed to the handler 598 599 Note that this cannot be called from a non-gui thread unless 600 wrapped in wx.CallAfter(). 601 """
602 - def __init__(self, *args, **kwargs):
603 604 self.pat = gmPerson.gmCurrentPatient() 605 self.debug = False 606 607 self.invalid_placeholder_template = _('invalid placeholder >>>>>%s<<<<<') 608 609 self.__injected_placeholders = {} 610 self.__cache = {} 611 612 self.__esc_style = None 613 self.__esc_func = lambda x:x 614 615 self.__ellipsis = None 616 self.__args_divider = '//' 617 self.__data_encoding = None 618 self.__data_encoding_strict = False
619 620 #-------------------------------------------------------- 621 # external API 622 #--------------------------------------------------------
623 - def set_placeholder(self, key=None, value=None, known_only=True):
624 _log.debug('setting [%s]', key) 625 try: 626 known_injectable_placeholders.index(key) 627 except ValueError: 628 _log.debug('injectable placeholder [%s] unknown', key) 629 if known_only: 630 raise 631 self.__injected_placeholders[key] = value
632 633 #--------------------------------------------------------
634 - def unset_placeholder(self, key=None):
635 _log.debug('unsetting [%s]', key) 636 try: 637 del self.__injected_placeholders[key] 638 except KeyError: 639 _log.debug('injectable placeholder [%s] unknown', key)
640 641 #--------------------------------------------------------
642 - def set_cache_value(self, key=None, value=None):
643 self.__cache[key] = value
644 #--------------------------------------------------------
645 - def unset_cache_value(self, key=None):
646 del self.__cache[key]
647 648 #--------------------------------------------------------
649 - def _set_escape_style(self, escape_style=None):
650 self.__esc_style = escape_style 651 return
652 653 escape_style = property(lambda x:x, _set_escape_style) 654 655 #--------------------------------------------------------
656 - def _set_escape_function(self, escape_function=None):
657 if escape_function is None: 658 self.__esc_func = lambda x:x 659 return 660 if not callable(escape_function): 661 raise ValueError('[%s._set_escape_function]: <%s> not callable' % (self.__class__.__name__, escape_function)) 662 self.__esc_func = escape_function 663 return
664 665 escape_function = property(lambda x:x, _set_escape_function) 666 667 #--------------------------------------------------------
668 - def _set_ellipsis(self, ellipsis):
669 if ellipsis == 'NONE': 670 ellipsis = None 671 self.__ellipsis = ellipsis
672 673 ellipsis = property(lambda x: self.__ellipsis, _set_ellipsis) 674 675 #--------------------------------------------------------
676 - def _set_arguments_divider(self, divider):
677 if divider == 'DEFAULT': 678 divider = '//' 679 self.__args_divider = divider
680 681 arguments_divider = property(lambda x: self.__args_divider, _set_arguments_divider) 682 683 #--------------------------------------------------------
684 - def _set_data_encoding(self, encoding):
685 if encoding == 'NONE': 686 self.__data_encoding = None 687 self.__data_encoding_strict = False 688 689 self.__data_encoding_strict = False 690 if encoding.endswith('-strict'): 691 self.__data_encoding_strict = True 692 encoding = encoding[:-7] 693 try: 694 codecs.lookup(encoding) 695 self.__data_encoding = encoding 696 except LookupError: 697 _log.error('<codecs> module can NOT handle encoding [%s]' % enc)
698 699 data_encoding = property(lambda x: self.__data_encoding, _set_data_encoding) 700 701 #-------------------------------------------------------- 702 placeholder_regex = property(lambda x: default_placeholder_regex, lambda x:x) 703 704 first_pass_placeholder_regex = property(lambda x: first_pass_placeholder_regex, lambda x:x) 705 second_pass_placeholder_regex = property(lambda x: second_pass_placeholder_regex, lambda x:x) 706 third_pass_placeholder_regex = property(lambda x: third_pass_placeholder_regex, lambda x:x) 707 708 #--------------------------------------------------------
709 - def __parse_region_definition(self, region_str):
710 region_str = region_str.strip() 711 712 if region_str == '': 713 return None, None 714 715 try: 716 pos_last_char = int(region_str) 717 return 0, pos_last_char 718 except (TypeError, ValueError): 719 _log.debug('region definition not a simple length') 720 721 # note that we only check for "legality", not for reasonable bounds 722 first_last = region_str.split('-') 723 if len(first_last) != 2: 724 _log.error('invalid placeholder region definition: %s', region_str) 725 raise ValueError 726 727 try: 728 pos_first_char = int(first_last[0].strip()) 729 pos_last_char = int(first_last[1].strip()) 730 except (TypeError, ValueError): 731 _log.error('invalid placeholder region definition: %s', region_str) 732 raise ValueError 733 734 # user says 1,2,... (= character position in string), Python needs 0,1,... (indexes 0-based) 735 if pos_first_char > 0: 736 pos_first_char -= 1 737 738 return pos_first_char, pos_last_char
739 740 #--------------------------------------------------------
741 - def __make_compatible_with_encoding(self, data_str):
742 if self.__data_encoding is None: 743 return data_str 744 745 try: 746 codecs.encode(data_str, self.__data_encoding, 'strict') 747 return data_str 748 except UnicodeEncodeError: 749 _log.error('cannot strict-encode string into [%s]: %s', self.__data_encoding, data_str) 750 751 if self.__data_encoding_strict: 752 return 'not compatible with encoding [%s]: %s' % (self.__data_encoding, data_str) 753 754 try: 755 import unidecode 756 except ImportError: 757 _log.debug('cannot transliterate, <unidecode> module not installed') 758 return codecs.encode(data_str, self.__data_encoding, 'replace').decode(self.__data_encoding) 759 760 return unidecode.unidecode(data_str).decode('utf8')
761 762 #-------------------------------------------------------- 763 # __getitem__ API 764 #--------------------------------------------------------
765 - def __getitem__(self, placeholder):
766 """Map self['placeholder'] to self.placeholder. 767 768 This is useful for replacing placeholders parsed out 769 of documents as strings. 770 771 Unknown/invalid placeholders still deliver a result but 772 it will be glaringly obvious if debugging is enabled. 773 """ 774 _log.debug('replacing [%s]', placeholder) 775 776 original_placeholder_def = placeholder 777 778 # remove leading/trailing '$<(<<)' and '(>>)>$' 779 if placeholder.startswith(default_placeholder_start): 780 placeholder = placeholder.lstrip('$').lstrip('<') 781 if placeholder.endswith(default_placeholder_end): 782 placeholder = placeholder.rstrip('$').rstrip('>') 783 else: 784 _log.error('placeholder must either start with [%s] and end with [%s] or neither of both', default_placeholder_start, default_placeholder_end) 785 if self.debug: 786 return self._escape(self.invalid_placeholder_template % original_placeholder_def) 787 return None 788 789 # injectable placeholder ? 790 parts = placeholder.split('::::', 1) 791 if len(parts) == 2: 792 ph_name, region_str = parts 793 is_an_injectable = True 794 try: 795 val = self.__injected_placeholders[ph_name] 796 except KeyError: 797 is_an_injectable = False 798 except: 799 _log.exception('injectable placeholder handling error: %s', original_placeholder_def) 800 if self.debug: 801 return self._escape(self.invalid_placeholder_template % original_placeholder_def) 802 return None 803 if is_an_injectable: 804 if val is None: 805 if self.debug: 806 return self._escape('injectable placeholder [%s]: no value available' % ph_name) 807 return placeholder 808 try: 809 pos_first_char, pos_last_char = self.__parse_region_definition(region_str) 810 except ValueError: 811 if self.debug: 812 return self._escape(self.invalid_placeholder_template % original_placeholder_def) 813 return None 814 if pos_last_char is None: 815 return self.__make_compatible_with_encoding(val) 816 # ellipsis needed ? 817 if len(val) > (pos_last_char - pos_first_char): 818 # ellipsis wanted ? 819 if self.__ellipsis is not None: 820 return self.__make_compatible_with_encoding(val[pos_first_char:(pos_last_char-len(self.__ellipsis))] + self.__ellipsis) 821 return self.__make_compatible_with_encoding(val[pos_first_char:pos_last_char]) 822 823 # variable placeholders 824 if len(placeholder.split('::', 2)) < 3: 825 _log.error('invalid placeholder structure: %s', original_placeholder_def) 826 if self.debug: 827 return self._escape(self.invalid_placeholder_template % original_placeholder_def) 828 return None 829 830 ph_name, data_and_lng = placeholder.split('::', 1) # note: split _is_ lsplit 831 options, region_str = data_and_lng.rsplit('::', 1) 832 _log.debug('placeholder parts: name=[%s]; region_def=[%s]; options=>>>%s<<<', ph_name, region_str, options) 833 try: 834 pos_first_char, pos_last_char = self.__parse_region_definition(region_str) 835 except ValueError: 836 if self.debug: 837 return self._escape(self.invalid_placeholder_template % original_placeholder_def) 838 return None 839 840 handler = getattr(self, '_get_variant_%s' % ph_name, None) 841 if handler is None: 842 _log.warning('no handler <_get_variant_%s> for placeholder %s', ph_name, original_placeholder_def) 843 if self.debug: 844 return self._escape(self.invalid_placeholder_template % original_placeholder_def) 845 return None 846 847 try: 848 val = handler(data = options) 849 if pos_last_char is None: 850 return self.__make_compatible_with_encoding(val) 851 # ellipsis needed ? 852 if len(val) > (pos_last_char - pos_first_char): 853 # ellipsis wanted ? 854 if self.__ellipsis is not None: 855 return self.__make_compatible_with_encoding(val[pos_first_char:(pos_last_char-len(self.__ellipsis))] + self.__ellipsis) 856 return self.__make_compatible_with_encoding(val[pos_first_char:pos_last_char]) 857 except: 858 _log.exception('placeholder handling error: %s', original_placeholder_def) 859 if self.debug: 860 return self._escape(self.invalid_placeholder_template % original_placeholder_def) 861 return None 862 863 _log.error('something went wrong, should never get here') 864 return None
865 866 #-------------------------------------------------------- 867 # placeholder handlers 868 #--------------------------------------------------------
869 - def _get_variant_ph_cfg(self, data=None):
870 options = data.split('//') # ALWAYS use '//' for splitting, regardless of self.__args_divider 871 name = options[0] 872 val = options[1] 873 if name == 'ellipsis': 874 self.ellipsis = val 875 elif name == 'argumentsdivider': 876 self.arguments_divider = val 877 elif name == 'encoding': 878 self.data_encoding = val 879 if len(options) > 2: 880 return options[2] % {'name': name, 'value': val} 881 return ''
882 #--------------------------------------------------------
883 - def _get_variant_client_version(self, data=None):
884 return self._escape ( 885 gmTools.coalesce ( 886 _cfg.get(option = 'client_version'), 887 '%s' % self.__class__.__name__ 888 ) 889 )
890 #--------------------------------------------------------
891 - def _get_variant_reminders(self, data=None):
892 893 from Gnumed.wxpython import gmProviderInboxWidgets 894 895 template = _('due %(due_date)s: %(comment)s (%(interval_due)s)') 896 date_format = '%Y %b %d' 897 898 data_parts = data.split(self.__args_divider) 899 900 if len(data_parts) > 0: 901 if data_parts[0].strip() != '': 902 template = data_parts[0] 903 904 if len(data_parts) > 1: 905 if data_parts[1].strip() != '': 906 date_format = data_parts[1] 907 908 reminders = gmProviderInboxWidgets.manage_reminders(patient = self.pat.ID) 909 910 if reminders is None: 911 return '' 912 913 if len(reminders) == 0: 914 return '' 915 916 lines = [ template % r.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for r in reminders ] 917 918 return '\n'.join(lines)
919 #--------------------------------------------------------
920 - def _get_variant_external_care(self, data=None):
921 922 from Gnumed.wxpython import gmExternalCareWidgets 923 external_cares = gmExternalCareWidgets.manage_external_care() 924 925 if external_cares is None: 926 return '' 927 928 if len(external_cares) == 0: 929 return '' 930 931 template = data 932 lines = [ template % ext.fields_as_dict(escape_style = self.__esc_style) for ext in external_cares ] 933 934 return '\n'.join(lines)
935 #--------------------------------------------------------
936 - def _get_variant_documents(self, data=None):
937 938 select = False 939 include_descriptions = False 940 template = '%s' 941 path_template = None 942 export_path = None 943 944 data_parts = data.split(self.__args_divider) 945 946 if 'select' in data_parts: 947 select = True 948 data_parts.remove('select') 949 950 if 'description' in data_parts: 951 include_descriptions = True 952 data_parts.remove('description') 953 954 template = data_parts[0] 955 956 if len(data_parts) > 1: 957 path_template = data_parts[1] 958 959 if len(data_parts) > 2: 960 export_path = data_parts[2] 961 962 # create path 963 if export_path is not None: 964 export_path = os.path.normcase(os.path.expanduser(export_path)) 965 gmTools.mkdir(export_path) 966 967 # select docs 968 if select: 969 docs = gmDocumentWidgets.manage_documents(msg = _('Select the patient documents to reference from the new document.'), single_selection = False) 970 else: 971 docs = self.pat.document_folder.documents 972 973 if docs is None: 974 return '' 975 976 lines = [] 977 for doc in docs: 978 lines.append(template % doc.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style)) 979 if include_descriptions: 980 for desc in doc.get_descriptions(max_lng = None): 981 lines.append(self._escape(desc['text'] + '\n')) 982 if path_template is not None: 983 for part_name in doc.save_parts_to_files(export_dir = export_path): 984 path, name = os.path.split(part_name) 985 lines.append(path_template % {'fullpath': part_name, 'name': name}) 986 987 return '\n'.join(lines)
988 #--------------------------------------------------------
989 - def _get_variant_encounter_list(self, data=None):
990 991 encounters = gmEncounterWidgets.select_encounters(single_selection = False) 992 if not encounters: 993 return '' 994 995 template = data 996 997 lines = [] 998 for enc in encounters: 999 try: 1000 lines.append(template % enc.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style)) 1001 except: 1002 lines.append('error formatting encounter') 1003 _log.exception('problem formatting encounter list') 1004 _log.error('template: %s', template) 1005 _log.error('encounter: %s', encounter) 1006 1007 return '\n'.join(lines)
1008 #--------------------------------------------------------
1009 - def _get_variant_soap_for_encounters(self, data=None):
1010 """Select encounters from list and format SOAP thereof. 1011 1012 data: soap_cats (' ' -> None -> admin) // date format 1013 """ 1014 # defaults 1015 cats = None 1016 date_format = None 1017 1018 if data is not None: 1019 data_parts = data.split(self.__args_divider) 1020 1021 # part[0]: categories 1022 if len(data_parts[0]) > 0: 1023 cats = [] 1024 if ' ' in data_parts[0]: 1025 cats.append(None) 1026 data_parts[0] = data_parts[0].replace(' ', '') 1027 cats.extend(list(data_parts[0])) 1028 1029 # part[1]: date format 1030 if len(data_parts) > 1: 1031 if len(data_parts[1]) > 0: 1032 date_format = data_parts[1] 1033 1034 encounters = gmEncounterWidgets.select_encounters(single_selection = False) 1035 if not encounters: 1036 return '' 1037 1038 chunks = [] 1039 for enc in encounters: 1040 chunks.append(enc.format_latex ( 1041 date_format = date_format, 1042 soap_cats = cats, 1043 soap_order = 'soap_rank, date' 1044 )) 1045 1046 return ''.join(chunks)
1047 #--------------------------------------------------------
1048 - def _get_variant_emr_journal(self, data=None):
1049 # default: all categories, neutral template 1050 cats = list('soapu') 1051 cats.append(None) 1052 template = '%s' 1053 interactive = True 1054 line_length = 9999 1055 time_range = None 1056 1057 if data is not None: 1058 data_parts = data.split(self.__args_divider) 1059 1060 # part[0]: categories 1061 cats = [] 1062 # ' ' -> None == admin 1063 for c in list(data_parts[0]): 1064 if c == ' ': 1065 c = None 1066 cats.append(c) 1067 # '' -> SOAP + None 1068 if cats == '': 1069 cats = list('soapu').append(None) 1070 1071 # part[1]: template 1072 if len(data_parts) > 1: 1073 template = data_parts[1] 1074 1075 # part[2]: line length 1076 if len(data_parts) > 2: 1077 try: 1078 line_length = int(data_parts[2]) 1079 except: 1080 line_length = 9999 1081 1082 # part[3]: weeks going back in time 1083 if len(data_parts) > 3: 1084 try: 1085 time_range = 7 * int(data_parts[3]) 1086 except: 1087 #time_range = None # infinite 1088 # pass on literally, meaning it must be a valid PG interval string 1089 time_range = data_parts[3] 1090 1091 # FIXME: will need to be a generator later on 1092 narr = self.pat.emr.get_as_journal(soap_cats = cats, time_range = time_range) 1093 1094 if len(narr) == 0: 1095 return '' 1096 1097 keys = narr[0].keys() 1098 lines = [] 1099 line_dict = {} 1100 for n in narr: 1101 for key in keys: 1102 if isinstance(n[key], str): 1103 line_dict[key] = self._escape(text = n[key]) 1104 continue 1105 line_dict[key] = n[key] 1106 try: 1107 lines.append((template % line_dict)[:line_length]) 1108 except KeyError: 1109 return 'invalid key in template [%s], valid keys: %s]' % (template, str(keys)) 1110 1111 return '\n'.join(lines)
1112 #--------------------------------------------------------
1113 - def _get_variant_soap_by_issue(self, data=None):
1114 return self.__get_variant_soap_by_issue_or_episode(data = data, mode = 'issue')
1115 #--------------------------------------------------------
1116 - def _get_variant_soap_by_episode(self, data=None):
1117 return self.__get_variant_soap_by_issue_or_episode(data = data, mode = 'episode')
1118 #--------------------------------------------------------
1119 - def __get_variant_soap_by_issue_or_episode(self, data=None, mode=None):
1120 1121 # default: all categories, neutral template 1122 cats = list('soapu') 1123 cats.append(None) 1124 1125 date_format = None 1126 template = '%s' 1127 1128 if data is not None: 1129 data_parts = data.split(self.__args_divider) 1130 1131 # part[0]: categories 1132 if len(data_parts[0]) > 0: 1133 cats = [] 1134 if ' ' in data_parts[0]: 1135 cats.append(None) 1136 cats.extend(list(data_parts[0].replace(' ', ''))) 1137 1138 # part[1]: date format 1139 if len(data_parts) > 1: 1140 if len(data_parts[1]) > 0: 1141 date_format = data_parts[1] 1142 1143 # part[2]: template 1144 if len(data_parts) > 2: 1145 if len(data_parts[2]) > 0: 1146 template = data_parts[2] 1147 1148 if mode == 'issue': 1149 narr = gmNarrativeWorkflows.select_narrative_by_issue(soap_cats = cats) 1150 else: 1151 narr = gmNarrativeWorkflows.select_narrative_by_episode(soap_cats = cats) 1152 1153 if narr is None: 1154 return '' 1155 1156 if len(narr) == 0: 1157 return '' 1158 1159 try: 1160 narr = [ template % n.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for n in narr ] 1161 except KeyError: 1162 return 'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys())) 1163 1164 return '\n'.join(narr)
1165 #--------------------------------------------------------
1166 - def _get_variant_progress_notes(self, data=None):
1167 return self._get_variant_soap(data = data)
1168 #--------------------------------------------------------
1169 - def _get_variant_soap_s(self, data=None):
1170 return self._get_variant_soap(data = 's')
1171 #--------------------------------------------------------
1172 - def _get_variant_soap_o(self, data=None):
1173 return self._get_variant_soap(data = 'o')
1174 #--------------------------------------------------------
1175 - def _get_variant_soap_a(self, data=None):
1176 return self._get_variant_soap(data = 'a')
1177 #--------------------------------------------------------
1178 - def _get_variant_soap_p(self, data=None):
1179 return self._get_variant_soap(data = 'p')
1180 #--------------------------------------------------------
1181 - def _get_variant_soap_u(self, data=None):
1182 return self._get_variant_soap(data = 'u')
1183 #--------------------------------------------------------
1184 - def _get_variant_soap_admin(self, data=None):
1185 return self._get_variant_soap(data = ' ')
1186 #--------------------------------------------------------
1187 - def _get_variant_soap(self, data=None):
1188 1189 # default: all categories, neutral template 1190 cats = list('soapu') 1191 cats.append(None) 1192 template = '%(narrative)s' 1193 1194 if data is not None: 1195 data_parts = data.split(self.__args_divider) 1196 1197 # part[0]: categories 1198 cats = [] 1199 # ' ' -> None == admin 1200 for cat in list(data_parts[0]): 1201 if cat == ' ': 1202 cat = None 1203 cats.append(cat) 1204 # '' -> SOAP + None 1205 if cats == '': 1206 cats = list('soapu') 1207 cats.append(None) 1208 1209 # part[1]: template 1210 if len(data_parts) > 1: 1211 template = data_parts[1] 1212 1213 #narr = gmNarrativeWorkflows.select_narrative_from_episodes(soap_cats = cats) 1214 narr = gmNarrativeWorkflows.select_narrative(soap_cats = cats) 1215 1216 if narr is None: 1217 return '' 1218 1219 if len(narr) == 0: 1220 return '' 1221 1222 # if any "%s" is in the template there cannot be any %(field)s 1223 # and we also restrict the fields to .narrative (this is the 1224 # old placeholder behaviour 1225 if '%s' in template: 1226 narr = [ self._escape(n['narrative']) for n in narr ] 1227 else: 1228 narr = [ n.fields_as_dict(escape_style = self.__esc_style) for n in narr ] 1229 1230 try: 1231 narr = [ template % n for n in narr ] 1232 except KeyError: 1233 return 'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys())) 1234 except TypeError: 1235 return 'cannot mix "%%s" and "%%(field)s" in template [%s]' % template 1236 1237 return '\n'.join(narr)
1238 1239 #--------------------------------------------------------
1240 - def _get_variant_title(self, data=None):
1241 return self._get_variant_name(data = '%(title)s')
1242 #--------------------------------------------------------
1243 - def _get_variant_firstname(self, data=None):
1244 return self._get_variant_name(data = '%(firstnames)s')
1245 #--------------------------------------------------------
1246 - def _get_variant_lastname(self, data=None):
1247 return self._get_variant_name(data = '%(lastnames)s')
1248 #--------------------------------------------------------
1249 - def _get_variant_name(self, data=None):
1250 if data is None: 1251 return [_('template is missing')] 1252 1253 name = self.pat.get_active_name() 1254 1255 parts = { 1256 'title': self._escape(gmTools.coalesce(name['title'], '')), 1257 'firstnames': self._escape(name['firstnames']), 1258 'lastnames': self._escape(name['lastnames']), 1259 'preferred': self._escape(gmTools.coalesce ( 1260 initial = name['preferred'], 1261 instead = ' ', 1262 template_initial = ' "%s" ' 1263 )) 1264 } 1265 1266 return data % parts
1267 1268 #--------------------------------------------------------
1269 - def _get_variant_date_of_birth(self, data='%Y %b %d'):
1270 return self.pat.get_formatted_dob(format = data)
1271 1272 #-------------------------------------------------------- 1273 # FIXME: extend to all supported genders
1274 - def _get_variant_gender_mapper(self, data='male//female//other'):
1275 1276 values = data.split('//', 2) 1277 1278 if len(values) == 2: 1279 male_value, female_value = values 1280 other_value = '<unkown gender>' 1281 elif len(values) == 3: 1282 male_value, female_value, other_value = values 1283 else: 1284 return _('invalid gender mapping layout: [%s]') % data 1285 1286 if self.pat['gender'] == 'm': 1287 return self._escape(male_value) 1288 1289 if self.pat['gender'] == 'f': 1290 return self._escape(female_value) 1291 1292 return self._escape(other_value)
1293 #-------------------------------------------------------- 1294 # address related placeholders 1295 #--------------------------------------------------------
1296 - def __get_variant_gen_adr_part(self, data='?', part=None):
1297 1298 template = '%s' 1299 msg = _('Select the address you want to use !') 1300 cache_id = '' 1301 options = data.split('//', 4) 1302 if len(options) > 0: 1303 template = options[0] 1304 if template.strip() == '': 1305 template = '%s' 1306 if len(options) > 1: 1307 msg = options[1] 1308 if len(options) > 2: 1309 cache_id = options[2] 1310 1311 cache_key = 'generic_address::' + cache_id 1312 try: 1313 adr2use = self.__cache[cache_key] 1314 _log.debug('cache hit (%s): [%s]', cache_key, adr2use) 1315 except KeyError: 1316 adr2use = None 1317 1318 if adr2use is None: 1319 dlg = gmAddressWidgets.cAddressSelectionDlg(None, -1) 1320 dlg.message = msg 1321 choice = dlg.ShowModal() 1322 adr2use = dlg.address 1323 dlg.Destroy() 1324 if choice == wx.ID_CANCEL: 1325 return '' 1326 self.__cache[cache_key] = adr2use 1327 1328 return template % self._escape(adr2use[part])
1329 #--------------------------------------------------------
1330 - def _get_variant_gen_adr_street(self, data='?'):
1331 return self.__get_variant_gen_adr_part(data = data, part = 'street')
1332 #--------------------------------------------------------
1333 - def _get_variant_gen_adr_number(self, data='?'):
1334 return self.__get_variant_gen_adr_part(data = data, part = 'number')
1335 #--------------------------------------------------------
1336 - def _get_variant_gen_adr_subunit(self, data='?'):
1337 return self.__get_variant_gen_adr_part(data = data, part = 'subunit')
1338 #--------------------------------------------------------
1339 - def _get_variant_gen_adr_location(self, data='?'):
1340 return self.__get_variant_gen_adr_part(data = data, part = 'urb')
1341 #--------------------------------------------------------
1342 - def _get_variant_gen_adr_suburb(self, data='?'):
1343 return self.__get_variant_gen_adr_part(data = data, part = 'suburb')
1344 #--------------------------------------------------------
1345 - def _get_variant_gen_adr_postcode(self, data='?'):
1346 return self.__get_variant_gen_adr_part(data = data, part = 'postcode')
1347 #--------------------------------------------------------
1348 - def _get_variant_gen_adr_region(self, data='?'):
1349 return self.__get_variant_gen_adr_part(data = data, part = 'l10n_region')
1350 #--------------------------------------------------------
1351 - def _get_variant_gen_adr_country(self, data='?'):
1352 return self.__get_variant_gen_adr_part(data = data, part = 'l10n_country')
1353 #--------------------------------------------------------
1354 - def __get_variant_receiver_part(self, data='%s', part=None):
1355 1356 template = '%s' 1357 cache_id = '' 1358 options = data.split('//', 3) 1359 if len(options) > 0: 1360 template = options[0] 1361 if template.strip() == '': 1362 template = '%s' 1363 if len(options) > 1: 1364 cache_id = options[1] 1365 1366 cache_key = 'receiver::' + cache_id 1367 try: 1368 name, adr = self.__cache[cache_key] 1369 _log.debug('cache hit (%s): [%s:%s]', cache_key, name, adr) 1370 except KeyError: 1371 name = None 1372 adr = None 1373 1374 if name is None: 1375 from Gnumed.wxpython import gmFormWidgets 1376 dlg = gmFormWidgets.cReceiverSelectionDlg(None, -1) 1377 dlg.patient = self.pat 1378 choice = dlg.ShowModal() 1379 name = dlg.name 1380 adr = dlg.address 1381 dlg.Destroy() 1382 if choice == wx.ID_CANCEL: 1383 return '' 1384 self.__cache[cache_key] = (name, adr) 1385 1386 if part == 'name': 1387 return template % self._escape(name) 1388 1389 return template % self._escape(gmTools.coalesce(adr[part], ''))
1390 #--------------------------------------------------------
1391 - def _get_variant_receiver_name(self, data='%s'):
1392 return self.__get_variant_receiver_part(data = data, part = 'name')
1393 #--------------------------------------------------------
1394 - def _get_variant_receiver_street(self, data='%s'):
1395 return self.__get_variant_receiver_part(data = data, part = 'street')
1396 #--------------------------------------------------------
1397 - def _get_variant_receiver_number(self, data='%s'):
1398 return self.__get_variant_receiver_part(data = data, part = 'number')
1399 #--------------------------------------------------------
1400 - def _get_variant_receiver_subunit(self, data='%s'):
1401 return self.__get_variant_receiver_part(data = data, part = 'subunit')
1402 #--------------------------------------------------------
1403 - def _get_variant_receiver_location(self, data='%s'):
1404 return self.__get_variant_receiver_part(data = data, part = 'urb')
1405 #--------------------------------------------------------
1406 - def _get_variant_receiver_suburb(self, data='%s'):
1407 return self.__get_variant_receiver_part(data = data, part = 'suburb')
1408 #--------------------------------------------------------
1409 - def _get_variant_receiver_postcode(self, data='%s'):
1410 return self.__get_variant_receiver_part(data = data, part = 'postcode')
1411 #--------------------------------------------------------
1412 - def _get_variant_receiver_region(self, data='%s'):
1413 return self.__get_variant_receiver_part(data = data, part = 'l10n_region')
1414 #--------------------------------------------------------
1415 - def _get_variant_receiver_country(self, data='%s'):
1416 return self.__get_variant_receiver_part(data = data, part = 'l10n_country')
1417 #--------------------------------------------------------
1418 - def _get_variant_patient_address(self, data=''):
1419 1420 data_parts = data.split(self.__args_divider) 1421 1422 # address type 1423 adr_type = data_parts[0].strip() 1424 orig_type = adr_type 1425 if adr_type != '': 1426 adrs = self.pat.get_addresses(address_type = adr_type) 1427 if len(adrs) == 0: 1428 _log.warning('no address for type [%s]', adr_type) 1429 adr_type = '' 1430 if adr_type == '': 1431 _log.debug('asking user for address type') 1432 adr = gmPersonContactWidgets.select_address(missing = orig_type, person = self.pat) 1433 if adr is None: 1434 if self.debug: 1435 return _('no address type replacement selected') 1436 return '' 1437 adr_type = adr['address_type'] 1438 adr = self.pat.get_addresses(address_type = adr_type)[0] 1439 1440 # formatting template 1441 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_region)s, %(l10n_country)s') 1442 if len(data_parts) > 1: 1443 if data_parts[1].strip() != '': 1444 template = data_parts[1] 1445 1446 try: 1447 return template % adr.fields_as_dict(escape_style = self.__esc_style) 1448 except Exception: 1449 _log.exception('error formatting address') 1450 _log.error('template: %s', template) 1451 1452 return None
1453 #--------------------------------------------------------
1454 - def __get_variant_adr_part(self, data='?', part=None):
1455 requested_type = data.strip() 1456 cache_key = 'adr-type-%s' % requested_type 1457 try: 1458 type2use = self.__cache[cache_key] 1459 _log.debug('cache hit (%s): [%s] -> [%s]', cache_key, requested_type, type2use) 1460 except KeyError: 1461 type2use = requested_type 1462 if type2use != '': 1463 adrs = self.pat.get_addresses(address_type = type2use) 1464 if len(adrs) == 0: 1465 _log.warning('no address of type [%s] for <%s> field extraction', requested_type, part) 1466 type2use = '' 1467 if type2use == '': 1468 _log.debug('asking user for replacement address type') 1469 adr = gmPersonContactWidgets.select_address(missing = requested_type, person = self.pat) 1470 if adr is None: 1471 _log.debug('no replacement selected') 1472 if self.debug: 1473 return self._escape(_('no address type replacement selected')) 1474 return '' 1475 type2use = adr['address_type'] 1476 self.__cache[cache_key] = type2use 1477 _log.debug('caching (%s): [%s] -> [%s]', cache_key, requested_type, type2use) 1478 1479 part_data = self.pat.get_addresses(address_type = type2use)[0][part] 1480 if part_data is None: 1481 part_data = '' # do escape empty string since we never know what target formats need 1482 return self._escape(part_data)
1483 1484 #--------------------------------------------------------
1485 - def _get_variant_adr_street(self, data='?'):
1486 return self.__get_variant_adr_part(data = data, part = 'street')
1487 #--------------------------------------------------------
1488 - def _get_variant_adr_number(self, data='?'):
1489 return self.__get_variant_adr_part(data = data, part = 'number')
1490 #--------------------------------------------------------
1491 - def _get_variant_adr_subunit(self, data='?'):
1492 return self.__get_variant_adr_part(data = data, part = 'subunit')
1493 #--------------------------------------------------------
1494 - def _get_variant_adr_location(self, data='?'):
1495 return self.__get_variant_adr_part(data = data, part = 'urb')
1496 #--------------------------------------------------------
1497 - def _get_variant_adr_suburb(self, data='?'):
1498 return self.__get_variant_adr_part(data = data, part = 'suburb')
1499 #--------------------------------------------------------
1500 - def _get_variant_adr_postcode(self, data='?'):
1501 return self.__get_variant_adr_part(data = data, part = 'postcode')
1502 #--------------------------------------------------------
1503 - def _get_variant_adr_region(self, data='?'):
1504 return self.__get_variant_adr_part(data = data, part = 'l10n_region')
1505 #--------------------------------------------------------
1506 - def _get_variant_adr_country(self, data='?'):
1507 return self.__get_variant_adr_part(data = data, part = 'l10n_country')
1508 #--------------------------------------------------------
1509 - def _get_variant_patient_comm(self, data=None):
1510 comm_type = None 1511 template = '%(url)s' 1512 if data is not None: 1513 data_parts = data.split(self.__args_divider) 1514 if len(data_parts) > 0: 1515 comm_type = data_parts[0] 1516 if len(data_parts) > 1: 1517 template = data_parts[1] 1518 1519 comms = self.pat.get_comm_channels(comm_medium = comm_type) 1520 if len(comms) == 0: 1521 if self.debug: 1522 return self._escape(_('no URL for comm channel [%s]') % data) 1523 return '' 1524 1525 return template % comms[0].fields_as_dict(escape_style = self.__esc_style)
1526 #--------------------------------------------------------
1527 - def _get_variant_patient_photo(self, data=None):
1528 1529 template = '%s' 1530 target_mime = None 1531 target_ext = None 1532 if data is not None: 1533 parts = data.split(self.__args_divider) 1534 template = parts[0] 1535 if len(parts) > 1: 1536 target_mime = parts[1].strip() 1537 if len(parts) > 2: 1538 target_ext = parts[2].strip() 1539 if target_ext is None: 1540 if target_mime is not None: 1541 target_ext = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime) 1542 1543 cache_key = 'patient_photo_path::%s::%s' % (target_mime, target_ext) 1544 try: 1545 fname = self.__cache[cache_key] 1546 _log.debug('cache hit on [%s]: %s', cache_key, fname) 1547 except KeyError: 1548 mugshot = self.pat.document_folder.latest_mugshot 1549 if mugshot is None: 1550 if self.debug: 1551 return self._escape(_('no mugshot available')) 1552 return '' 1553 fname = mugshot.save_to_file ( 1554 target_mime = target_mime, 1555 target_extension = target_ext, 1556 ignore_conversion_problems = True 1557 ) 1558 if fname is None: 1559 if self.debug: 1560 return self._escape(_('cannot export or convert latest mugshot')) 1561 return '' 1562 self.__cache[cache_key] = fname 1563 1564 return template % fname
1565 1566 #--------------------------------------------------------
1567 - def _get_variant_patient_vcf(self, data):
1568 options = data.split(self.__args_divider) 1569 template = options[0].strip() 1570 if template == '': 1571 template = '%s' 1572 1573 return template % self.pat.export_as_vcard()
1574 1575 #--------------------------------------------------------
1576 - def _get_variant_patient_mcf(self, data):
1577 template = u'%s' 1578 format = 'txt' 1579 options = data.split(self.__args_divider) 1580 _log.debug('options: %s', options) 1581 for o in options: 1582 if o.strip().startswith('fmt='): 1583 format = o.strip()[4:] 1584 if format not in ['qr', 'mcf', 'txt']: 1585 return self._escape(_('patient_mcf: invalid format (qr/mcf/txt)')) 1586 continue 1587 if o.strip().startswith('tmpl='): 1588 template = o.strip()[5:] 1589 continue 1590 _log.debug('template: %s' % template) 1591 _log.debug('format: %s' % format) 1592 1593 if format == 'txt': 1594 return template % self._escape(self.pat.MECARD) 1595 1596 if format == 'mcf': 1597 return template % self.pat.export_as_mecard() 1598 1599 if format == 'qr': 1600 qr_filename = gmTools.create_qrcode(text = self.pat.MECARD) 1601 if qr_filename is None: 1602 return self._escape('patient_mcf-cannot_create_QR_code') 1603 return template % qr_filename 1604 1605 return None
1606 1607 #--------------------------------------------------------
1608 - def _get_variant_patient_gdt(self, data):
1609 options = data.split(self.__args_divider) 1610 template = options[0].strip() 1611 if template == '': 1612 template = '%s' 1613 1614 return template % self.pat.export_as_gdt()
1615 1616 #--------------------------------------------------------
1617 - def _get_variant_patient_tags(self, data='%s//\\n'):
1618 if len(self.pat.tags) == 0: 1619 if self.debug: 1620 return self._escape(_('no tags for this patient')) 1621 return '' 1622 1623 tags = gmDemographicsWidgets.select_patient_tags(patient = self.pat) 1624 1625 if tags is None: 1626 if self.debug: 1627 return self._escape(_('no patient tags selected for inclusion') % data) 1628 return '' 1629 1630 template, separator = data.split('//', 2) 1631 1632 return separator.join([ template % t.fields_as_dict(escape_style = self.__esc_style) for t in tags ])
1633 # #-------------------------------------------------------- 1634 # def _get_variant_patient_tags_table(self, data=u'?'): 1635 # pass 1636 #-------------------------------------------------------- 1637 # praxis related placeholders 1638 #--------------------------------------------------------
1639 - def _get_variant_praxis(self, data=None):
1640 options = data.split(self.__args_divider) 1641 1642 if 'select' in options: 1643 options.remove('select') 1644 branch = 'select branch' 1645 else: 1646 branch = gmPraxis.cPraxisBranch(aPK_obj = gmPraxis.gmCurrentPraxisBranch()['pk_praxis_branch']) 1647 1648 template = '%s' 1649 if len(options) > 0: 1650 template = options[0] 1651 if template.strip() == '': 1652 template = '%s' 1653 1654 return template % branch.fields_as_dict(escape_style = self.__esc_style)
1655 1656 #--------------------------------------------------------
1657 - def _get_variant_praxis_vcf(self, data=None):
1658 1659 cache_key = 'current_branch_vcf_path' 1660 try: 1661 vcf_name = self.__cache[cache_key] 1662 _log.debug('cache hit (%s): [%s]', cache_key, vcf_name) 1663 except KeyError: 1664 vcf_name = gmPraxis.gmCurrentPraxisBranch().vcf 1665 self.__cache[cache_key] = vcf_name 1666 1667 template = '%s' 1668 if data.strip() != '': 1669 template = data 1670 1671 return template % vcf_name
1672 1673 #--------------------------------------------------------
1674 - def _get_variant_praxis_mcf(self, data):
1675 template = u'%s' 1676 format = 'txt' 1677 options = data.split(self.__args_divider) 1678 _log.debug('options: %s', options) 1679 for o in options: 1680 if o.strip().startswith('fmt='): 1681 format = o.strip()[4:] 1682 if format not in ['qr', 'mcf', 'txt']: 1683 return self._escape(_('praxis_mcf: invalid format (qr/mcf/txt)')) 1684 continue 1685 if o.strip().startswith('tmpl='): 1686 template = o.strip()[5:] 1687 continue 1688 _log.debug('template: %s' % template) 1689 _log.debug('format: %s' % format) 1690 1691 if format == 'txt': 1692 return template % self._escape(gmPraxis.gmCurrentPraxisBranch().MECARD) 1693 1694 if format == 'mcf': 1695 return template % gmPraxis.gmCurrentPraxisBranch().export_as_mecard() 1696 1697 if format == 'qr': 1698 qr_filename = gmTools.create_qrcode(text = gmPraxis.gmCurrentPraxisBranch().MECARD) 1699 if qr_filename is None: 1700 return self._escape('praxis_mcf-cannot_create_QR_code') 1701 return template % qr_filename 1702 1703 return None
1704 1705 #--------------------------------------------------------
1706 - def _get_variant_praxis_address(self, data=''):
1707 options = data.split(self.__args_divider) 1708 1709 # formatting template 1710 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_region)s, %(l10n_country)s') 1711 if len(options) > 0: 1712 if options[0].strip() != '': 1713 template = options[0] 1714 1715 adr = gmPraxis.gmCurrentPraxisBranch().address 1716 if adr is None: 1717 if self.debug: 1718 return _('no address recorded') 1719 return '' 1720 try: 1721 return template % adr.fields_as_dict(escape_style = self.__esc_style) 1722 except Exception: 1723 _log.exception('error formatting address') 1724 _log.error('template: %s', template) 1725 1726 return None
1727 1728 #--------------------------------------------------------
1729 - def _get_variant_praxis_comm(self, data=None):
1730 options = data.split(self.__args_divider) 1731 comm_type = options[0] 1732 template = '%(url)s' 1733 if len(options) > 1: 1734 template = options[1] 1735 1736 comms = gmPraxis.gmCurrentPraxisBranch().get_comm_channels(comm_medium = comm_type) 1737 if len(comms) == 0: 1738 if self.debug: 1739 return self._escape(_('no URL for comm channel [%s]') % data) 1740 return '' 1741 1742 return template % comms[0].fields_as_dict(escape_style = self.__esc_style)
1743 1744 #--------------------------------------------------------
1745 - def _get_variant_praxis_id(self, data=None):
1746 options = data.split(self.__args_divider) 1747 id_type = options[0].strip() 1748 if id_type == '': 1749 return self._escape('praxis external ID: type is missing') 1750 1751 if len(options) > 1: 1752 issuer = options[1].strip() 1753 if issuer == '': 1754 issue = None 1755 else: 1756 issuer = None 1757 1758 if len(options) > 2: 1759 template = options[2] 1760 else: 1761 template = '%(name)s: %(value)s (%(issuer)s)' 1762 1763 ids = gmPraxis.gmCurrentPraxisBranch().get_external_ids(id_type = id_type, issuer = issuer) 1764 if len(ids) == 0: 1765 if self.debug: 1766 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer)) 1767 return '' 1768 1769 return template % self._escape_dict(the_dict = ids[0], none_string = '')
1770 1771 #-------------------------------------------------------- 1772 # provider related placeholders 1773 #--------------------------------------------------------
1774 - def _get_variant_current_provider(self, data=None):
1775 prov = gmStaff.gmCurrentProvider() 1776 1777 tmp = '%s%s. %s' % ( 1778 gmTools.coalesce(prov['title'], '', '%s '), 1779 prov['firstnames'][:1], 1780 prov['lastnames'] 1781 ) 1782 return self._escape(tmp)
1783 1784 #--------------------------------------------------------
1785 - def _get_variant_current_provider_title(self, data=None):
1786 if data is None: 1787 template = u'%(title)s' 1788 elif data.strip() == u'': 1789 data = u'%(title)s' 1790 return self._get_variant_current_provider_name(data = data)
1791 1792 #--------------------------------------------------------
1793 - def _get_variant_current_provider_firstnames(self, data=None):
1794 if data is None: 1795 data = u'%(firstnames)s' 1796 elif data.strip() == u'': 1797 data = u'%(firstnames)s' 1798 return self._get_variant_current_provider_name(data = data)
1799 1800 #--------------------------------------------------------
1801 - def _get_variant_current_provider_lastnames(self, data=None):
1802 if data is None: 1803 data = u'%(lastnames)s' 1804 elif data.strip() == u'': 1805 data = u'%(lastnames)s' 1806 return self._get_variant_current_provider_name(data = data)
1807 1808 #--------------------------------------------------------
1809 - def _get_variant_current_provider_name(self, data=None):
1810 if data is None: 1811 return [_('template is missing')] 1812 if data.strip() == '': 1813 return [_('template is empty')] 1814 name = gmStaff.gmCurrentProvider().identity.get_active_name() 1815 parts = { 1816 'title': self._escape(gmTools.coalesce(name['title'], '')), 1817 'firstnames': self._escape(name['firstnames']), 1818 'lastnames': self._escape(name['lastnames']), 1819 'preferred': self._escape(gmTools.coalesce(name['preferred'], '')) 1820 } 1821 return data % parts
1822 1823 #--------------------------------------------------------
1825 data_parts = data.split(self.__args_divider) 1826 if len(data_parts) < 2: 1827 return self._escape('current provider external ID: template is missing') 1828 1829 id_type = data_parts[0].strip() 1830 if id_type == '': 1831 return self._escape('current provider external ID: type is missing') 1832 1833 issuer = data_parts[1].strip() 1834 if issuer == '': 1835 return self._escape('current provider external ID: issuer is missing') 1836 1837 prov = gmStaff.gmCurrentProvider() 1838 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer) 1839 1840 if len(ids) == 0: 1841 if self.debug: 1842 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer)) 1843 return '' 1844 1845 return self._escape(ids[0]['value'])
1846 1847 #--------------------------------------------------------
1848 - def _get_variant_primary_praxis_provider(self, data=None):
1849 prov = self.pat.primary_provider 1850 if prov is None: 1851 return self._get_variant_current_provider() 1852 1853 title = gmTools.coalesce ( 1854 prov['title'], 1855 gmPerson.map_gender2salutation(prov['gender']) 1856 ) 1857 1858 tmp = '%s %s. %s' % ( 1859 title, 1860 prov['firstnames'][:1], 1861 prov['lastnames'] 1862 ) 1863 return self._escape(tmp)
1864 1865 #--------------------------------------------------------
1867 data_parts = data.split(self.__args_divider) 1868 if len(data_parts) < 2: 1869 return self._escape('primary in-praxis provider external ID: template is missing') 1870 1871 id_type = data_parts[0].strip() 1872 if id_type == '': 1873 return self._escape('primary in-praxis provider external ID: type is missing') 1874 1875 issuer = data_parts[1].strip() 1876 if issuer == '': 1877 return self._escape('primary in-praxis provider external ID: issuer is missing') 1878 1879 prov = self.pat.primary_provider 1880 if prov is None: 1881 if self.debug: 1882 return self._escape(_('no primary in-praxis provider')) 1883 return '' 1884 1885 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer) 1886 1887 if len(ids) == 0: 1888 if self.debug: 1889 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer)) 1890 return '' 1891 1892 return self._escape(ids[0]['value'])
1893 1894 #--------------------------------------------------------
1895 - def _get_variant_external_id(self, data=''):
1896 data_parts = data.split(self.__args_divider) 1897 if len(data_parts) < 2: 1898 return self._escape('patient external ID: template is missing') 1899 1900 id_type = data_parts[0].strip() 1901 if id_type == '': 1902 return self._escape('patient external ID: type is missing') 1903 1904 issuer = data_parts[1].strip() 1905 if issuer == '': 1906 return self._escape('patient external ID: issuer is missing') 1907 1908 ids = self.pat.get_external_ids(id_type = id_type, issuer = issuer) 1909 1910 if len(ids) == 0: 1911 if self.debug: 1912 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer)) 1913 return '' 1914 1915 return self._escape(ids[0]['value'])
1916 1917 #--------------------------------------------------------
1918 - def _get_variant_allergy_state(self, data=None):
1919 allg_state = self.pat.emr.allergy_state 1920 1921 if allg_state['last_confirmed'] is None: 1922 date_confirmed = '' 1923 else: 1924 date_confirmed = ' (%s)' % gmDateTime.pydt_strftime ( 1925 allg_state['last_confirmed'], 1926 format = '%Y %B %d' 1927 ) 1928 1929 tmp = '%s%s' % ( 1930 allg_state.state_string, 1931 date_confirmed 1932 ) 1933 return self._escape(tmp)
1934 1935 #--------------------------------------------------------
1936 - def _get_variant_allergy_list(self, data=None):
1937 if data is None: 1938 return self._escape(_('template is missing')) 1939 1940 template, separator = data.split('//', 2) 1941 1942 return separator.join([ template % a.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) for a in self.pat.emr.get_allergies() ])
1943 1944 #--------------------------------------------------------
1945 - def _get_variant_allergies(self, data=None):
1946 1947 if data is None: 1948 return self._escape(_('template is missing')) 1949 1950 return '\n'.join([ data % a.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) for a in self.pat.emr.get_allergies() ])
1951 1952 #--------------------------------------------------------
1953 - def _get_variant_current_meds_AMTS_enhanced(self, data=None):
1954 return self._get_variant_current_meds_AMTS(data=data, strict=False)
1955 1956 #--------------------------------------------------------
1957 - def _get_variant_current_meds_AMTS(self, data=None, strict=True):
1958 1959 # select intakes 1960 emr = self.pat.emr 1961 from Gnumed.wxpython import gmMedicationWidgets 1962 intakes2export = gmMedicationWidgets.manage_substance_intakes(emr = emr) 1963 if intakes2export is None: 1964 return '' 1965 if len(intakes2export) == 0: 1966 return '' 1967 1968 # make them unique: 1969 unique_intakes = {} 1970 for intake in intakes2export: 1971 if intake['pk_drug_product'] is None: 1972 unique_intakes[intake['pk_substance']] = intake 1973 else: 1974 unique_intakes[intake['product']] = intake 1975 del intakes2export 1976 unique_intakes = unique_intakes.values() 1977 1978 # create data files / datamatrix code files 1979 self.__create_amts_datamatrix_files(intakes = unique_intakes) 1980 1981 # create AMTS-LaTeX per intake 1982 intake_as_latex_rows = [] 1983 for intake in unique_intakes: 1984 intake_as_latex_rows.append(intake._get_as_amts_latex(strict = strict)) 1985 del unique_intakes 1986 1987 # append allergy information 1988 # - state 1989 intake_as_latex_rows.extend(emr.allergy_state._get_as_amts_latex(strict = strict)) 1990 # - allergies 1991 for allg in emr.get_allergies(): 1992 intake_as_latex_rows.append(allg._get_as_amts_latex(strict = strict)) 1993 1994 # insert \newpage after each group of 15 rows 1995 table_rows = intake_as_latex_rows[:15] 1996 if len(intake_as_latex_rows) > 15: 1997 table_rows.append('\\newpage') 1998 table_rows.extend(intake_as_latex_rows[15:30]) 1999 if len(intake_as_latex_rows) > 30: 2000 table_rows.append('\\newpage') 2001 table_rows.extend(intake_as_latex_rows[30:45]) 2002 2003 if strict: 2004 return '\n'.join(table_rows) 2005 2006 # allow two more pages in enhanced mode 2007 if len(intake_as_latex_rows) > 45: 2008 table_rows.append('\\newpage') 2009 table_rows.extend(intake_as_latex_rows[30:45]) 2010 2011 if len(intake_as_latex_rows) > 60: 2012 table_rows.append('\\newpage') 2013 table_rows.extend(intake_as_latex_rows[30:45]) 2014 2015 return '\n'.join(table_rows)
2016 2017 #--------------------------------------------------------
2018 - def __create_amts_datamatrix_files(self, intakes=None):
2019 2020 # setup dummy files 2021 for idx in [1,2,3]: 2022 self.set_placeholder(key = 'amts_data_file_%s' % idx, value = './missing-file.txt', known_only = False) 2023 self.set_placeholder(key = 'amts_png_file_%s' % idx, value = './missing-file.png', known_only = False) 2024 self.set_placeholder(key = 'amts_png_file_current_page', value = './missing-file-current-page.png', known_only = False) 2025 self.set_placeholder(key = 'amts_png_file_utf8', value = './missing-file-utf8.png', known_only = False) 2026 self.set_placeholder(key = 'amts_data_file_utf8', value = './missing-file-utf8.txt', known_only = False) 2027 2028 # find processor 2029 found, dmtx_creator = gmShellAPI.detect_external_binary(binary = 'gm-create_datamatrix') 2030 _log.debug(dmtx_creator) 2031 if not found: 2032 _log.error('gm-create_datamatrix(.bat/.exe) not found') 2033 return 2034 2035 png_dir = gmTools.mk_sandbox_dir() 2036 _log.debug('sandboxing AMTS datamatrix PNGs in: %s', png_dir) 2037 2038 from Gnumed.business import gmForms 2039 2040 # generate GNUmed-enhanced non-conformant data file and datamatrix 2041 # for embedding (utf8, unabridged data fields) 2042 amts_data_template_def_file = gmMedication.generate_amts_data_template_definition_file(strict = False) 2043 _log.debug('amts data template definition file: %s', amts_data_template_def_file) 2044 form = gmForms.cTextForm(template_file = amts_data_template_def_file) 2045 # <S>ection with intakes</S> 2046 amts_sections = '<S>%s</S>' % ''.join ([ 2047 i._get_as_amts_data(strict = False) for i in intakes 2048 ]) 2049 # <S>ection with allergy data</S> 2050 emr = self.pat.emr 2051 amts_sections += emr.allergy_state._get_as_amts_data(strict = False) % ''.join ([ 2052 a._get_as_amts_data(strict = False) for a in emr.get_allergies() 2053 ]) 2054 self.set_placeholder(key = 'amts_intakes_as_data_enhanced', value = amts_sections, known_only = False) 2055 # self.set_placeholder(key = u'amts_check_symbol', value = gmMedication.calculate_amts_data_check_symbol(intakes = intakes), known_only = False) 2056 self.set_placeholder(key = 'amts_total_pages', value = '1', known_only = False) 2057 success = form.substitute_placeholders(data_source = self) 2058 self.unset_placeholder(key = 'amts_intakes_as_data_enhanced') 2059 # self.unset_placeholder(key = u'amts_check_symbol') 2060 self.unset_placeholder(key = 'amts_total_pages') 2061 if not success: 2062 _log.error('cannot substitute into amts data file form template') 2063 return 2064 data_file = form.re_editable_filenames[0] 2065 png_file = os.path.join(png_dir, 'gm4amts-datamatrix-utf8.png') 2066 cmd = '%s %s %s' % (dmtx_creator, data_file, png_file) 2067 success = gmShellAPI.run_command_in_shell(command = cmd, blocking = True) 2068 if not success: 2069 _log.error('error running [%s]' % cmd) 2070 return 2071 self.set_placeholder(key = 'amts_data_file_utf8', value = data_file, known_only = False) 2072 self.set_placeholder(key = 'amts_png_file_utf8', value = png_file, known_only = False) 2073 2074 # generate conformant per-page files: 2075 total_pages = (len(intakes) / 15.0) 2076 if total_pages > int(total_pages): 2077 total_pages += 1 2078 total_pages = int(total_pages) 2079 _log.debug('total pages: %s', total_pages) 2080 2081 png_file_base = os.path.join(png_dir, 'gm4amts-datamatrix-page-') 2082 for this_page in range(1,total_pages+1): 2083 intakes_this_page = intakes[(this_page-1)*15:this_page*15] 2084 amts_data_template_def_file = gmMedication.generate_amts_data_template_definition_file(strict = True) 2085 _log.debug('amts data template definition file: %s', amts_data_template_def_file) 2086 form = gmForms.cTextForm(template_file = amts_data_template_def_file) 2087 # <S>ection with intakes</S> 2088 amts_sections = '<S>%s</S>' % ''.join ([ 2089 i._get_as_amts_data(strict = False) for i in intakes_this_page 2090 ]) 2091 if this_page == total_pages: 2092 # <S>ection with allergy data</S> 2093 emr = self.pat.emr 2094 amts_sections += emr.allergy_state._get_as_amts_data(strict = False) % ''.join ([ 2095 a._get_as_amts_data(strict = False) for a in emr.get_allergies() 2096 ]) 2097 self.set_placeholder(key = 'amts_intakes_as_data', value = amts_sections, known_only = False) 2098 # self.set_placeholder(key = u'amts_check_symbol', value = gmMedication.calculate_amts_data_check_symbol(intakes = intakes_this_page), known_only = False) 2099 if total_pages == 1: 2100 pg_idx = '' 2101 else: 2102 pg_idx = '%s' % this_page 2103 self.set_placeholder(key = 'amts_page_idx', value = pg_idx, known_only = False) 2104 self.set_placeholder(key = 'amts_total_pages', value = '%s' % total_pages, known_only = False) 2105 success = form.substitute_placeholders(data_source = self) 2106 self.unset_placeholder(key = 'amts_intakes_as_data') 2107 # self.unset_placeholder(key = u'amts_check_symbol') 2108 self.unset_placeholder(key = 'amts_page_idx') 2109 self.unset_placeholder(key = 'amts_total_pages') 2110 if not success: 2111 _log.error('cannot substitute into amts data file form template') 2112 return 2113 2114 data_file = form.re_editable_filenames[0] 2115 png_file = '%s%s.png' % (png_file_base, this_page) 2116 latin1_data_file = gmTools.recode_file ( 2117 source_file = data_file, 2118 source_encoding = 'utf8', 2119 target_encoding = 'latin1', 2120 base_dir = os.path.split(data_file)[0] 2121 ) 2122 cmd = '%s %s %s' % (dmtx_creator, latin1_data_file, png_file) 2123 success = gmShellAPI.run_command_in_shell(command = cmd, blocking = True) 2124 if not success: 2125 _log.error('error running [%s]' % cmd) 2126 return 2127 2128 # cache file names for later use in \embedfile 2129 self.set_placeholder(key = 'amts_data_file_%s' % this_page, value = latin1_data_file, known_only = False) 2130 self.set_placeholder(key = 'amts_png_file_%s' % this_page, value = png_file, known_only = False) 2131 2132 self.set_placeholder(key = 'amts_png_file_current_page', value = png_file_base + '\\thepage', known_only = False)
2133 2134 #--------------------------------------------------------
2135 - def _get_variant_current_meds_for_rx(self, data=None):
2136 if data is None: 2137 return self._escape(_('current_meds_for_rx: template is missing')) 2138 2139 emr = self.pat.emr 2140 from Gnumed.wxpython import gmMedicationWidgets 2141 current_meds = gmMedicationWidgets.manage_substance_intakes(emr = emr) 2142 if current_meds is None: 2143 return '' 2144 2145 intakes2show = {} 2146 for intake in current_meds: 2147 fields_dict = intake.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) 2148 fields_dict['medically_formatted_start'] = self._escape(intake.medically_formatted_start) 2149 if intake['pk_drug_product'] is None: 2150 fields_dict['product'] = self._escape(_('generic %s') % fields_dict['substance']) 2151 fields_dict['contains'] = self._escape('%s %s%s' % (fields_dict['substance'], fields_dict['amount'], fields_dict['unit'])) 2152 intakes2show[fields_dict['product']] = fields_dict 2153 else: 2154 comps = [ c.split('::') for c in intake.containing_drug['components'] ] 2155 fields_dict['contains'] = self._escape('; '.join([ '%s %s%s' % (c[0], c[1], c[2]) for c in comps ])) 2156 intakes2show[intake['product']] = fields_dict # this will make multi-component drugs unique 2157 2158 intakes2dispense = {} 2159 for product, intake in intakes2show.items(): 2160 msg = _('Dispense how much/many of "%(product)s (%(contains)s)" ?') % intake 2161 amount2dispense = wx.GetTextFromUser(msg, _('Amount to dispense ?')) 2162 if amount2dispense == '': 2163 continue 2164 intake['amount2dispense'] = amount2dispense 2165 intakes2dispense[product] = intake 2166 2167 return '\n'.join([ data % intake for intake in intakes2dispense.values() ])
2168 2169 #--------------------------------------------------------
2170 - def _get_variant_substance_abuse(self, data=None):
2171 if data is None: 2172 return self._escape(_('template is missing')) 2173 template = data 2174 from Gnumed.wxpython import gmHabitWidgets 2175 abuses = gmHabitWidgets.manage_substance_abuse(patient = self.pat) 2176 if abuses is None: 2177 return '' 2178 lines = [] 2179 for a in abuses: 2180 fields = a.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) 2181 fields['harmful_use_type'] = a.harmful_use_type_string 2182 lines.append(template % fields) 2183 return '\n'.join(lines)
2184 2185 #--------------------------------------------------------
2186 - def _get_variant_current_meds(self, data=None):
2187 2188 if data is None: 2189 return self._escape(_('template is missing')) 2190 2191 parts = data.split(self.__args_divider) 2192 template = parts[0] 2193 ask_user = False 2194 if len(parts) > 1: 2195 ask_user = (parts[1] == 'select') 2196 2197 emr = self.pat.emr 2198 if ask_user: 2199 from Gnumed.wxpython import gmMedicationWidgets 2200 current_meds = gmMedicationWidgets.manage_substance_intakes(emr = emr) 2201 if current_meds is None: 2202 return '' 2203 else: 2204 current_meds = emr.get_current_medications ( 2205 include_inactive = False, 2206 include_unapproved = True, 2207 order_by = 'product, substance' 2208 ) 2209 if len(current_meds) == 0: 2210 return '' 2211 2212 lines = [] 2213 for m in current_meds: 2214 data = m.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) 2215 data['medically_formatted_start'] = self._escape(m.medically_formatted_start) 2216 lines.append(template % data) 2217 2218 return '\n'.join(lines)
2219 #--------------------------------------------------------
2220 - def _get_variant_current_meds_table(self, data=None):
2221 return gmMedication.format_substance_intake ( 2222 emr = self.pat.emr, 2223 output_format = self.__esc_style, 2224 table_type = 'by-product' 2225 )
2226 #--------------------------------------------------------
2227 - def _get_variant_current_meds_notes(self, data=None):
2228 return gmMedication.format_substance_intake_notes ( 2229 emr = self.pat.emr, 2230 output_format = self.__esc_style, 2231 table_type = 'by-product' 2232 )
2233 #--------------------------------------------------------
2234 - def _get_variant_lab_table(self, data=None):
2235 return gmPathLab.format_test_results ( 2236 results = self.pat.emr.get_test_results_by_date(), 2237 output_format = self.__esc_style 2238 )
2239 #--------------------------------------------------------
2240 - def _get_variant_test_results(self, data=None):
2241 2242 template = '' 2243 date_format = '%Y %b %d %H:%M' 2244 separator = '\n' 2245 2246 options = data.split(self.__args_divider) 2247 try: 2248 template = options[0].strip() 2249 date_format = options[1] 2250 separator = options[2] 2251 except IndexError: 2252 pass 2253 2254 if date_format.strip() == '': 2255 date_format = '%Y %b %d %H:%M' 2256 if separator.strip() == '': 2257 separator = '\n' 2258 2259 #results = gmMeasurementWidgets.manage_measurements(single_selection = False, emr = self.pat.emr) 2260 from Gnumed.wxpython.gmMeasurementWidgets import manage_measurements 2261 results = manage_measurements(single_selection = False, emr = self.pat.emr) 2262 if results is None: 2263 if self.debug: 2264 return self._escape(_('no results for this patient (available or selected)')) 2265 return '' 2266 2267 if template == '': 2268 return (separator + separator).join([ self._escape(r.format(date_format = date_format)) for r in results ]) 2269 2270 return separator.join([ template % r.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for r in results ])
2271 #--------------------------------------------------------
2272 - def _get_variant_latest_vaccs_table(self, data=None):
2273 return gmVaccination.format_latest_vaccinations ( 2274 output_format = self.__esc_style, 2275 emr = self.pat.emr 2276 )
2277 #--------------------------------------------------------
2278 - def _get_variant_vaccination_history(self, data=None):
2279 options = data.split(self.__args_divider) 2280 template = options[0] 2281 if len(options) > 1: 2282 date_format = options[1] 2283 else: 2284 date_format = '%Y %b %d' 2285 vaccinations_as_dict = [] 2286 for v in self.pat.emr.get_vaccinations(order_by = 'date_given DESC, vaccine'): 2287 v_as_dict = v.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) 2288 v_as_dict['l10n_indications'] = [ ind['l10n_indication'] for ind in v['indications'] ] 2289 vaccinations_as_dict.append(v_as_dict) 2290 2291 return u'\n'.join([ template % v for v in vaccinations_as_dict ])
2292 2293 #--------------------------------------------------------
2294 - def _get_variant_PHX(self, data=None):
2295 2296 if data is None: 2297 if self.debug: 2298 _log.error('PHX: missing placeholder arguments') 2299 return self._escape(_('PHX: Invalid placeholder options.')) 2300 return '' 2301 2302 _log.debug('arguments: %s', data) 2303 2304 data_parts = data.split(self.__args_divider) 2305 template = '%s' 2306 separator = '\n' 2307 date_format = '%Y %b %d' 2308 try: 2309 template = data_parts[0] 2310 separator = data_parts[1] 2311 date_format = data_parts[2] 2312 except IndexError: 2313 pass 2314 2315 phxs = gmEMRStructWidgets.select_health_issues(emr = self.pat.emr) 2316 if phxs is None: 2317 if self.debug: 2318 return self._escape(_('no PHX for this patient (available or selected)')) 2319 return '' 2320 2321 return separator.join ([ 2322 template % phx.fields_as_dict ( 2323 date_format = date_format, 2324 escape_style = self.__esc_style, 2325 bool_strings = (self._escape(_('yes')), self._escape(_('no'))) 2326 ) for phx in phxs 2327 ])
2328 2329 #--------------------------------------------------------
2330 - def _get_variant_problems(self, data=None):
2331 2332 if data is None: 2333 return self._escape(_('template is missing')) 2334 probs = self.pat.emr.get_problems() 2335 return '\n'.join([ data % p.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) for p in probs ])
2336 2337 #--------------------------------------------------------
2338 - def _get_variant_diagnoses(self, data=None):
2339 2340 if data is None: 2341 return self._escape(_('template is missing')) 2342 template = data 2343 dxs = self.pat.emr.candidate_diagnoses 2344 if len(dxs) == 0: 2345 _log.debug('no diagnoses available') 2346 return '' 2347 selected = gmListWidgets.get_choices_from_list ( 2348 msg = _('Select the relevant diagnoses:'), 2349 caption = _('Diagnosis selection'), 2350 columns = [ _('Diagnosis'), _('Marked confidential'), _('Certainty'), _('Source') ], 2351 choices = [[ 2352 dx['diagnosis'], 2353 gmTools.bool2subst(dx['explicitely_confidential'], _('yes'), _('no'), _('unknown')), 2354 gmTools.coalesce(dx['diagnostic_certainty_classification'], ''), 2355 dx['source'] 2356 ] for dx in dxs 2357 ], 2358 data = dxs, 2359 single_selection = False, 2360 can_return_empty = True 2361 ) 2362 if selected is None: 2363 _log.debug('user did not select any diagnoses') 2364 return '' 2365 if len(selected) == 0: 2366 _log.debug('user did not select any diagnoses') 2367 return '' 2368 #return template % {'diagnosis': u'', 'diagnostic_certainty_classification': u''} 2369 return '\n'.join(template % self._escape_dict(dx, none_string = '?', bool_strings = [_('yes'), _('no')]) for dx in selected)
2370 2371 #--------------------------------------------------------
2372 - def _get_variant_today(self, data='%Y %b %d'):
2373 return self._escape(gmDateTime.pydt_now_here().strftime(data))
2374 2375 #--------------------------------------------------------
2376 - def _get_variant_tex_escape(self, data=None):
2378 2379 #--------------------------------------------------------
2380 - def _get_variant_url_escape(self, data=None):
2381 return self._escape(urllib.parse.quote(data.encode('utf8')))
2382 2383 #--------------------------------------------------------
2384 - def _get_variant_text_snippet(self, data=None):
2385 data_parts = data.split(self.__args_divider) 2386 keyword = data_parts[0] 2387 template = '%s' 2388 if len(data_parts) > 1: 2389 template = data_parts[1] 2390 2391 expansion = gmKeywordExpansionWidgets.expand_keyword(keyword = keyword, show_list_if_needed = True) 2392 2393 if expansion is None: 2394 if self.debug: 2395 return self._escape(_('no textual expansion found for keyword <%s>') % keyword) 2396 return '' 2397 2398 #return template % self._escape(expansion) 2399 return template % expansion
2400 2401 #--------------------------------------------------------
2402 - def _get_variant_data_snippet(self, data=None):
2403 parts = data.split(self.__args_divider) 2404 keyword = parts[0] 2405 template = '%s' 2406 target_mime = None 2407 target_ext = None 2408 if len(parts) > 1: 2409 template = parts[1] 2410 if len(parts) > 2: 2411 target_mime = parts[2].strip() 2412 if len(parts) > 3: 2413 target_ext = parts[3].strip() 2414 if target_ext is None: 2415 if target_mime is not None: 2416 target_ext = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime) 2417 2418 expansion = gmKeywordExpansion.get_expansion ( 2419 keyword = keyword, 2420 textual_only = False, 2421 binary_only = True 2422 ) 2423 if expansion is None: 2424 if self.debug: 2425 return self._escape(_('no binary expansion found for keyword <%s>') % keyword) 2426 return '' 2427 2428 filename = expansion.save_to_file() 2429 if filename is None: 2430 if self.debug: 2431 return self._escape(_('cannot export data of binary expansion keyword <%s>') % keyword) 2432 return '' 2433 2434 if expansion['is_encrypted']: 2435 pwd = wx.GetPasswordFromUser ( 2436 message = _('Enter your GnuPG passphrase for decryption of [%s]') % expansion['keyword'], 2437 caption = _('GnuPG passphrase prompt'), 2438 default_value = '' 2439 ) 2440 filename = gmCrypto.gpg_decrypt_file(filename = filename, passphrase = pwd) 2441 if filename is None: 2442 if self.debug: 2443 return self._escape(_('cannot decrypt data of binary expansion keyword <%s>') % keyword) 2444 return '' 2445 2446 target_fname = gmTools.get_unique_filename ( 2447 prefix = '%s-converted-' % os.path.splitext(filename)[0], 2448 suffix = target_ext 2449 ) 2450 if not gmMimeLib.convert_file(filename = filename, target_mime = target_mime, target_filename = target_fname): 2451 if self.debug: 2452 return self._escape(_('cannot convert data of binary expansion keyword <%s>') % keyword) 2453 # hoping that the target can cope: 2454 return template % filename 2455 2456 return template % target_fname
2457 2458 #--------------------------------------------------------
2459 - def _get_variant_qrcode(self, data=None):
2460 options = data.split(self.__args_divider) 2461 if len(options) == 0: 2462 return None 2463 text4qr = options[0] 2464 if len(options) > 1: 2465 template = options[1] 2466 else: 2467 template = u'%s' 2468 qr_filename = gmTools.create_qrcode(text = text4qr) 2469 if qr_filename is None: 2470 return self._escape('cannot_create_QR_code') 2471 2472 return template % qr_filename
2473 2474 #--------------------------------------------------------
2475 - def _get_variant_range_of(self, data=None):
2476 if data is None: 2477 return None 2478 # wrapper code already takes care of actually 2479 # selecting the range so all we need to do here 2480 # is to return the data itself 2481 return data
2482 2483 #--------------------------------------------------------
2484 - def _get_variant_if_not_empty(self, data=None):
2485 if data is None: 2486 return None 2487 2488 parts = data.split(self.__args_divider) 2489 if len(parts) < 3: 2490 return 'IF_NOT_EMPTY lacks <instead> definition' 2491 txt = parts[0] 2492 template = parts[1] 2493 instead = parts[2] 2494 2495 if txt.strip() == '': 2496 return instead 2497 if '%s' in template: 2498 return template % txt 2499 return template
2500 2501 #--------------------------------------------------------
2502 - def _get_variant_if_debugging(self, data=None):
2503 2504 if data is None: 2505 return None 2506 parts = data.split(self.__args_divider) 2507 if len(parts) < 2: 2508 return self._escape(u'IF_DEBUGGING lacks proper definition') 2509 debug_str = parts[0] 2510 non_debug_str = parts[1] 2511 if self.debug: 2512 return debug_str 2513 return non_debug_str
2514 2515 #--------------------------------------------------------
2516 - def _get_variant_free_text(self, data=None):
2517 2518 if data is None: 2519 parts = [] 2520 msg = _('generic text') 2521 cache_key = 'free_text::%s' % datetime.datetime.now() 2522 else: 2523 parts = data.split(self.__args_divider) 2524 msg = parts[0] 2525 cache_key = 'free_text::%s' % msg 2526 2527 try: 2528 return self.__cache[cache_key] 2529 except KeyError: 2530 pass 2531 2532 if len(parts) > 1: 2533 preset = parts[1] 2534 else: 2535 preset = '' 2536 2537 dlg = gmGuiHelpers.cMultilineTextEntryDlg ( 2538 None, 2539 -1, 2540 title = _('Replacing <free_text> placeholder'), 2541 msg = _('Below you can enter free text.\n\n [%s]') % msg, 2542 text = preset 2543 ) 2544 dlg.enable_user_formatting = True 2545 decision = dlg.ShowModal() 2546 text = dlg.value.strip() 2547 is_user_formatted = dlg.is_user_formatted 2548 dlg.Destroy() 2549 2550 if decision != wx.ID_SAVE: 2551 if self.debug: 2552 return self._escape(_('Text input cancelled by user.')) 2553 return self._escape('') 2554 2555 # user knows "best" 2556 if is_user_formatted: 2557 self.__cache[cache_key] = text 2558 return text 2559 2560 text = self._escape(text) 2561 self.__cache[cache_key] = text 2562 return text
2563 2564 #--------------------------------------------------------
2565 - def _get_variant_bill(self, data=None):
2566 try: 2567 bill = self.__cache['bill'] 2568 except KeyError: 2569 from Gnumed.wxpython import gmBillingWidgets 2570 bill = gmBillingWidgets.manage_bills(patient = self.pat) 2571 if bill is None: 2572 if self.debug: 2573 return self._escape(_('no bill selected')) 2574 return '' 2575 self.__cache['bill'] = bill 2576 2577 parts = data.split(self.__args_divider) 2578 template = parts[0] 2579 if len(parts) > 1: 2580 date_format = parts[1] 2581 else: 2582 date_format = '%Y %B %d' 2583 2584 return template % bill.fields_as_dict(date_format = date_format, escape_style = self.__esc_style)
2585 2586 #--------------------------------------------------------
2587 - def _get_variant_bill_item(self, data=None):
2588 try: 2589 bill = self.__cache['bill'] 2590 except KeyError: 2591 from Gnumed.wxpython import gmBillingWidgets 2592 bill = gmBillingWidgets.manage_bills(patient = self.pat) 2593 if bill is None: 2594 if self.debug: 2595 return self._escape(_('no bill selected')) 2596 return '' 2597 self.__cache['bill'] = bill 2598 2599 parts = data.split(self.__args_divider) 2600 template = parts[0] 2601 if len(parts) > 1: 2602 date_format = parts[1] 2603 else: 2604 date_format = '%Y %B %d' 2605 2606 return '\n'.join([ template % i.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for i in bill.bill_items ])
2607 2608 #--------------------------------------------------------
2609 - def __get_variant_bill_adr_part(self, data=None, part=None):
2610 try: 2611 bill = self.__cache['bill'] 2612 except KeyError: 2613 from Gnumed.wxpython import gmBillingWidgets 2614 bill = gmBillingWidgets.manage_bills(patient = self.pat) 2615 if bill is None: 2616 if self.debug: 2617 return self._escape(_('no bill selected')) 2618 return '' 2619 self.__cache['bill'] = bill 2620 self.__cache['bill-adr'] = bill.address 2621 2622 try: 2623 bill_adr = self.__cache['bill-adr'] 2624 except KeyError: 2625 bill_adr = bill.address 2626 self.__cache['bill-adr'] = bill_adr 2627 2628 if bill_adr is None: 2629 if self.debug: 2630 return self._escape(_('[%s] bill has no address') % part) 2631 return '' 2632 2633 if bill_adr[part] is None: 2634 return self._escape('') 2635 2636 if data is None: 2637 return self._escape(bill_adr[part]) 2638 2639 if data == '': 2640 return self._escape(bill_adr[part]) 2641 2642 return data % self._escape(bill_adr[part])
2643 2644 #--------------------------------------------------------
2645 - def _get_variant_bill_adr_street(self, data='?'):
2646 return self.__get_variant_bill_adr_part(data = data, part = 'street')
2647 2648 #--------------------------------------------------------
2649 - def _get_variant_bill_adr_number(self, data='?'):
2650 return self.__get_variant_bill_adr_part(data = data, part = 'number')
2651 2652 #--------------------------------------------------------
2653 - def _get_variant_bill_adr_subunit(self, data='?'):
2654 return self.__get_variant_bill_adr_part(data = data, part = 'subunit')
2655 #--------------------------------------------------------
2656 - def _get_variant_bill_adr_location(self, data='?'):
2657 return self.__get_variant_bill_adr_part(data = data, part = 'urb')
2658 2659 #--------------------------------------------------------
2660 - def _get_variant_bill_adr_suburb(self, data='?'):
2661 return self.__get_variant_bill_adr_part(data = data, part = 'suburb')
2662 2663 #--------------------------------------------------------
2664 - def _get_variant_bill_adr_postcode(self, data='?'):
2665 return self.__get_variant_bill_adr_part(data = data, part = 'postcode')
2666 2667 #--------------------------------------------------------
2668 - def _get_variant_bill_adr_region(self, data='?'):
2669 return self.__get_variant_bill_adr_part(data = data, part = 'l10n_region')
2670 2671 #--------------------------------------------------------
2672 - def _get_variant_bill_adr_country(self, data='?'):
2673 return self.__get_variant_bill_adr_part(data = data, part = 'l10n_country')
2674 2675 #-------------------------------------------------------- 2676 # internal helpers 2677 #--------------------------------------------------------
2678 - def _escape(self, text=None):
2679 if self.__esc_func is None: 2680 return text 2681 assert (text is not None), 'text=None passed to _escape()' 2682 return self.__esc_func(text)
2683 2684 #--------------------------------------------------------
2685 - def _escape_dict(self, the_dict=None, date_format='%Y %b %d %H:%M', none_string='', bool_strings=None):
2686 if bool_strings is None: 2687 bools = {True: _('true'), False: _('false')} 2688 else: 2689 bools = {True: bool_strings[0], False: bool_strings[1]} 2690 data = {} 2691 for field in the_dict.keys(): 2692 # FIXME: harden against BYTEA fields 2693 #if type(self._payload[self._idx[field]]) == ... 2694 # data[field] = _('<%s bytes of binary data>') % len(self._payload[self._idx[field]]) 2695 # continue 2696 val = the_dict[field] 2697 if val is None: 2698 data[field] = none_string 2699 continue 2700 if isinstance(val, bool): 2701 data[field] = bools[val] 2702 continue 2703 if isinstance(val, datetime.datetime): 2704 data[field] = gmDateTime.pydt_strftime(val, format = date_format) 2705 if self.__esc_style in ['latex', 'tex']: 2706 data[field] = gmTools.tex_escape_string(data[field]) 2707 elif self.__esc_style in ['xetex', 'xelatex']: 2708 data[field] = gmTools.xetex_escape_string(data[field]) 2709 continue 2710 try: 2711 data[field] = str(val, encoding = 'utf8', errors = 'replace') 2712 except TypeError: 2713 try: 2714 data[field] = str(val) 2715 except (UnicodeDecodeError, TypeError): 2716 val = '%s' % str(val) 2717 data[field] = val.decode('utf8', 'replace') 2718 if self.__esc_style in ['latex', 'tex']: 2719 data[field] = gmTools.tex_escape_string(data[field]) 2720 elif self.__esc_style in ['xetex', 'xelatex']: 2721 data[field] = gmTools.xetex_escape_string(data[field]) 2722 return data
2723 2724 #---------------------------------------------------------------------
2725 -def test_placeholders():
2726 2727 _log.debug('testing for placeholders with pattern: %s', first_pass_placeholder_regex) 2728 2729 data_source = gmPlaceholderHandler() 2730 original_line = '' 2731 2732 while True: 2733 # get input from user 2734 line = wx.GetTextFromUser ( 2735 _('Enter some text containing a placeholder:'), 2736 _('Testing placeholders'), 2737 centre = True, 2738 default_value = original_line 2739 ) 2740 if line.strip() == '': 2741 break 2742 original_line = line 2743 # replace 2744 placeholders_in_line = regex.findall(first_pass_placeholder_regex, line, regex.IGNORECASE) 2745 if len(placeholders_in_line) == 0: 2746 continue 2747 for placeholder in placeholders_in_line: 2748 try: 2749 val = data_source[placeholder] 2750 except: 2751 val = _('error with placeholder [%s]') % placeholder 2752 if val is None: 2753 val = _('error with placeholder [%s]') % placeholder 2754 line = line.replace(placeholder, val) 2755 # show 2756 msg = _( 2757 'Input: %s\n' 2758 '\n' 2759 'Output:\n' 2760 '%s' 2761 ) % ( 2762 original_line, 2763 line 2764 ) 2765 gmGuiHelpers.gm_show_info ( 2766 title = _('Testing placeholders'), 2767 info = msg 2768 )
2769 2770 #=====================================================================
2771 -class cMacroPrimitives:
2772 """Functions a macro can legally use. 2773 2774 An instance of this class is passed to the GNUmed scripting 2775 listener. Hence, all actions a macro can legally take must 2776 be defined in this class. Thus we achieve some screening for 2777 security and also thread safety handling. 2778 """ 2779 #-----------------------------------------------------------------
2780 - def __init__(self, personality = None):
2781 if personality is None: 2782 raise gmExceptions.ConstructorError('must specify personality') 2783 self.__personality = personality 2784 self.__attached = 0 2785 self._get_source_personality = None 2786 self.__user_done = False 2787 self.__user_answer = 'no answer yet' 2788 self.__pat = gmPerson.gmCurrentPatient() 2789 2790 self.__auth_cookie = str(random.random()) 2791 self.__pat_lock_cookie = str(random.random()) 2792 self.__lock_after_load_cookie = str(random.random()) 2793 2794 _log.info('slave mode personality is [%s]', personality)
2795 #----------------------------------------------------------------- 2796 # public API 2797 #-----------------------------------------------------------------
2798 - def attach(self, personality = None):
2799 if self.__attached: 2800 _log.error('attach with [%s] rejected, already serving a client', personality) 2801 return (0, _('attach rejected, already serving a client')) 2802 if personality != self.__personality: 2803 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality)) 2804 return (0, _('attach to personality [%s] rejected') % personality) 2805 self.__attached = 1 2806 self.__auth_cookie = str(random.random()) 2807 return (1, self.__auth_cookie)
2808 #-----------------------------------------------------------------
2809 - def detach(self, auth_cookie=None):
2810 if not self.__attached: 2811 return 1 2812 if auth_cookie != self.__auth_cookie: 2813 _log.error('rejecting detach() with cookie [%s]' % auth_cookie) 2814 return 0 2815 self.__attached = 0 2816 return 1
2817 #-----------------------------------------------------------------
2818 - def force_detach(self):
2819 if not self.__attached: 2820 return 1 2821 self.__user_done = False 2822 # FIXME: use self.__sync_cookie for syncing with user interaction 2823 wx.CallAfter(self._force_detach) 2824 return 1
2825 #-----------------------------------------------------------------
2826 - def version(self):
2827 ver = _cfg.get(option = 'client_version') 2828 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
2829 #-----------------------------------------------------------------
2830 - def shutdown_gnumed(self, auth_cookie=None, forced=False):
2831 """Shuts down this client instance.""" 2832 if not self.__attached: 2833 return 0 2834 if auth_cookie != self.__auth_cookie: 2835 _log.error('non-authenticated shutdown_gnumed()') 2836 return 0 2837 wx.CallAfter(self._shutdown_gnumed, forced) 2838 return 1
2839 #-----------------------------------------------------------------
2840 - def raise_gnumed(self, auth_cookie = None):
2841 """Raise ourselves to the top of the desktop.""" 2842 if not self.__attached: 2843 return 0 2844 if auth_cookie != self.__auth_cookie: 2845 _log.error('non-authenticated raise_gnumed()') 2846 return 0 2847 return "cMacroPrimitives.raise_gnumed() not implemented"
2848 #-----------------------------------------------------------------
2849 - def get_loaded_plugins(self, auth_cookie = None):
2850 if not self.__attached: 2851 return 0 2852 if auth_cookie != self.__auth_cookie: 2853 _log.error('non-authenticated get_loaded_plugins()') 2854 return 0 2855 gb = gmGuiBroker.GuiBroker() 2856 return gb['horstspace.notebook.gui'].keys()
2857 #-----------------------------------------------------------------
2858 - def raise_notebook_plugin(self, auth_cookie = None, a_plugin = None):
2859 """Raise a notebook plugin within GNUmed.""" 2860 if not self.__attached: 2861 return 0 2862 if auth_cookie != self.__auth_cookie: 2863 _log.error('non-authenticated raise_notebook_plugin()') 2864 return 0 2865 # FIXME: use semaphore 2866 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin) 2867 return 1
2868 #-----------------------------------------------------------------
2869 - def load_patient_from_external_source(self, auth_cookie = None):
2870 """Load external patient, perhaps create it. 2871 2872 Callers must use get_user_answer() to get status information. 2873 It is unsafe to proceed without knowing the completion state as 2874 the controlled client may be waiting for user input from a 2875 patient selection list. 2876 """ 2877 if not self.__attached: 2878 return (0, _('request rejected, you are not attach()ed')) 2879 if auth_cookie != self.__auth_cookie: 2880 _log.error('non-authenticated load_patient_from_external_source()') 2881 return (0, _('rejected load_patient_from_external_source(), not authenticated')) 2882 if self.__pat.locked: 2883 _log.error('patient is locked, cannot load from external source') 2884 return (0, _('current patient is locked')) 2885 self.__user_done = False 2886 wx.CallAfter(self._load_patient_from_external_source) 2887 self.__lock_after_load_cookie = str(random.random()) 2888 return (1, self.__lock_after_load_cookie)
2889 #-----------------------------------------------------------------
2890 - def lock_loaded_patient(self, auth_cookie = None, lock_after_load_cookie = None):
2891 if not self.__attached: 2892 return (0, _('request rejected, you are not attach()ed')) 2893 if auth_cookie != self.__auth_cookie: 2894 _log.error('non-authenticated lock_load_patient()') 2895 return (0, _('rejected lock_load_patient(), not authenticated')) 2896 # FIXME: ask user what to do about wrong cookie 2897 if lock_after_load_cookie != self.__lock_after_load_cookie: 2898 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie) 2899 return (0, 'patient lock-after-load request rejected, wrong cookie provided') 2900 self.__pat.locked = True 2901 self.__pat_lock_cookie = str(random.random()) 2902 return (1, self.__pat_lock_cookie)
2903 #-----------------------------------------------------------------
2904 - def lock_into_patient(self, auth_cookie = None, search_params = None):
2905 if not self.__attached: 2906 return (0, _('request rejected, you are not attach()ed')) 2907 if auth_cookie != self.__auth_cookie: 2908 _log.error('non-authenticated lock_into_patient()') 2909 return (0, _('rejected lock_into_patient(), not authenticated')) 2910 if self.__pat.locked: 2911 _log.error('patient is already locked') 2912 return (0, _('already locked into a patient')) 2913 searcher = gmPersonSearch.cPatientSearcher_SQL() 2914 if type(search_params) == dict: 2915 idents = searcher.get_identities(search_dict=search_params) 2916 raise Exception("must use dto, not search_dict") 2917 else: 2918 idents = searcher.get_identities(search_term=search_params) 2919 if idents is None: 2920 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict)) 2921 if len(idents) == 0: 2922 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict)) 2923 # FIXME: let user select patient 2924 if len(idents) > 1: 2925 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict)) 2926 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]): 2927 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict)) 2928 self.__pat.locked = True 2929 self.__pat_lock_cookie = str(random.random()) 2930 return (1, self.__pat_lock_cookie)
2931 #-----------------------------------------------------------------
2932 - def unlock_patient(self, auth_cookie = None, unlock_cookie = None):
2933 if not self.__attached: 2934 return (0, _('request rejected, you are not attach()ed')) 2935 if auth_cookie != self.__auth_cookie: 2936 _log.error('non-authenticated unlock_patient()') 2937 return (0, _('rejected unlock_patient, not authenticated')) 2938 # we ain't locked anyways, so succeed 2939 if not self.__pat.locked: 2940 return (1, '') 2941 # FIXME: ask user what to do about wrong cookie 2942 if unlock_cookie != self.__pat_lock_cookie: 2943 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie) 2944 return (0, 'patient unlock request rejected, wrong cookie provided') 2945 self.__pat.locked = False 2946 return (1, '')
2947 #-----------------------------------------------------------------
2948 - def assume_staff_identity(self, auth_cookie = None, staff_name = "Dr.Jekyll", staff_creds = None):
2949 if not self.__attached: 2950 return 0 2951 if auth_cookie != self.__auth_cookie: 2952 _log.error('non-authenticated select_identity()') 2953 return 0 2954 return "cMacroPrimitives.assume_staff_identity() not implemented"
2955 #-----------------------------------------------------------------
2956 - def get_user_answer(self):
2957 if not self.__user_done: 2958 return (0, 'still waiting') 2959 self.__user_done = False 2960 return (1, self.__user_answer)
2961 #----------------------------------------------------------------- 2962 # internal API 2963 #-----------------------------------------------------------------
2964 - def _force_detach(self):
2965 msg = _( 2966 'Someone tries to forcibly break the existing\n' 2967 'controlling connection. This may or may not\n' 2968 'have legitimate reasons.\n\n' 2969 'Do you want to allow breaking the connection ?' 2970 ) 2971 can_break_conn = gmGuiHelpers.gm_show_question ( 2972 aMessage = msg, 2973 aTitle = _('forced detach attempt') 2974 ) 2975 if can_break_conn: 2976 self.__user_answer = 1 2977 else: 2978 self.__user_answer = 0 2979 self.__user_done = True 2980 if can_break_conn: 2981 self.__pat.locked = False 2982 self.__attached = 0 2983 return 1
2984 #-----------------------------------------------------------------
2985 - def _shutdown_gnumed(self, forced=False):
2986 top_win = wx.GetApp().GetTopWindow() 2987 if forced: 2988 top_win.Destroy() 2989 else: 2990 top_win.Close()
2991 #-----------------------------------------------------------------
2993 patient = gmPatSearchWidgets.get_person_from_external_sources(search_immediately = True, activate_immediately = True) 2994 if patient is not None: 2995 self.__user_answer = 1 2996 else: 2997 self.__user_answer = 0 2998 self.__user_done = True 2999 return 1
3000 #===================================================================== 3001 # main 3002 #===================================================================== 3003 if __name__ == '__main__': 3004 3005 if len(sys.argv) < 2: 3006 sys.exit() 3007 3008 if sys.argv[1] != 'test': 3009 sys.exit() 3010 3011 gmI18N.activate_locale() 3012 gmI18N.install_domain() 3013 3014 #--------------------------------------------------------
3015 - def test_placeholders():
3016 handler = gmPlaceholderHandler() 3017 handler.debug = True 3018 3019 for placeholder in ['a', 'b']: 3020 print(handler[placeholder]) 3021 3022 pat = gmPersonSearch.ask_for_patient() 3023 if pat is None: 3024 return 3025 3026 gmPatSearchWidgets.set_active_patient(patient = pat) 3027 3028 print('DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']) 3029 3030 app = wx.PyWidgetTester(size = (200, 50)) 3031 3032 ph = 'progress_notes::ap' 3033 print('%s: %s' % (ph, handler[ph]))
3034 #--------------------------------------------------------
3035 - def test_new_variant_placeholders():
3036 3037 tests = [ 3038 # should work: 3039 '$<lastname>$', 3040 '$<lastname::::3>$', 3041 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$', 3042 3043 # should fail: 3044 'lastname', 3045 '$<lastname', 3046 '$<lastname::', 3047 '$<lastname::>$', 3048 '$<lastname::abc>$', 3049 '$<lastname::abc::>$', 3050 '$<lastname::abc::3>$', 3051 '$<lastname::abc::xyz>$', 3052 '$<lastname::::>$', 3053 '$<lastname::::xyz>$', 3054 3055 '$<date_of_birth::%Y-%m-%d>$', 3056 '$<date_of_birth::%Y-%m-%d::3>$', 3057 '$<date_of_birth::%Y-%m-%d::>$', 3058 3059 # should work: 3060 '$<adr_location::home::35>$', 3061 '$<gender_mapper::male//female//other::5>$', 3062 '$<current_meds::==> %(product)s %(preparation)s (%(substance)s) <==\n::50>$', 3063 '$<allergy_list::%(descriptor)s, >$', 3064 '$<current_meds_table::latex//>$' 3065 3066 # 'firstname', 3067 # 'title', 3068 # 'date_of_birth', 3069 # 'progress_notes', 3070 # 'soap', 3071 # 'soap_s', 3072 # 'soap_o', 3073 # 'soap_a', 3074 # 'soap_p', 3075 3076 # 'soap', 3077 # 'progress_notes', 3078 # 'date_of_birth' 3079 ] 3080 3081 # tests = [ 3082 # '$<latest_vaccs_table::latex>$' 3083 # ] 3084 3085 pat = gmPersonSearch.ask_for_patient() 3086 if pat is None: 3087 return 3088 3089 gmPatSearchWidgets.set_active_patient(patient = pat) 3090 3091 handler = gmPlaceholderHandler() 3092 handler.debug = True 3093 3094 for placeholder in tests: 3095 print(placeholder, "=>", handler[placeholder]) 3096 print("--------------") 3097 input()
3098 3099 # print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d'] 3100 3101 # app = wx.PyWidgetTester(size = (200, 50)) 3102 3103 # ph = 'progress_notes::ap' 3104 # print '%s: %s' % (ph, handler[ph]) 3105 3106 #--------------------------------------------------------
3107 - def test_scripting():
3108 from Gnumed.pycommon import gmScriptingListener 3109 import xmlrpc.client 3110 3111 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999) 3112 3113 s = xmlrpc.client.ServerProxy('http://localhost:9999') 3114 print("should fail:", s.attach()) 3115 print("should fail:", s.attach('wrong cookie')) 3116 print("should work:", s.version()) 3117 print("should fail:", s.raise_gnumed()) 3118 print("should fail:", s.raise_notebook_plugin('test plugin')) 3119 print("should fail:", s.lock_into_patient('kirk, james')) 3120 print("should fail:", s.unlock_patient()) 3121 status, conn_auth = s.attach('unit test') 3122 print("should work:", status, conn_auth) 3123 print("should work:", s.version()) 3124 print("should work:", s.raise_gnumed(conn_auth)) 3125 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james') 3126 print("should work:", status, pat_auth) 3127 print("should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie')) 3128 print("should work", s.unlock_patient(conn_auth, pat_auth)) 3129 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'} 3130 status, pat_auth = s.lock_into_patient(conn_auth, data) 3131 print("should work:", status, pat_auth) 3132 print("should work", s.unlock_patient(conn_auth, pat_auth)) 3133 print(s.detach('bogus detach cookie')) 3134 print(s.detach(conn_auth)) 3135 del s 3136 3137 listener.shutdown()
3138 #--------------------------------------------------------
3139 - def test_placeholder_regex():
3140 3141 import re as regex 3142 3143 tests = [ 3144 ' $<lastname>$ ', 3145 ' $<lastname::::3>$ ', 3146 3147 # should fail: 3148 '$<date_of_birth::%Y-%m-%d>$', 3149 '$<date_of_birth::%Y-%m-%d::3>$', 3150 '$<date_of_birth::%Y-%m-%d::>$', 3151 3152 '$<adr_location::home::35>$', 3153 '$<gender_mapper::male//female//other::5>$', 3154 '$<current_meds::==> %(product)s %(preparation)s (%(substance)s) <==\\n::50>$', 3155 '$<allergy_list::%(descriptor)s, >$', 3156 3157 '\\noindent Patient: $<lastname>$, $<firstname>$', 3158 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$', 3159 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(product)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$' 3160 ] 3161 3162 tests = [ 3163 3164 'junk $<lastname::::3>$ junk', 3165 'junk $<lastname::abc::3>$ junk', 3166 'junk $<lastname::abc>$ junk', 3167 'junk $<lastname>$ junk', 3168 3169 'junk $<lastname>$ junk $<firstname>$ junk', 3170 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk', 3171 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk', 3172 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk' 3173 3174 ] 3175 3176 tests = [ 3177 # u'junk $<<<date_of_birth::%Y %B %d $<inner placeholder::%Y %B %d::20>$::20>>>$ junk', 3178 # u'junk $<date_of_birth::%Y %B %d::20>$ $<date_of_birth::%Y %B %d::20>$', 3179 # u'junk $<date_of_birth::%Y %B %d::>$ $<date_of_birth::%Y %B %d::20>$ $<<date_of_birth::%Y %B %d::20>>$', 3180 # u'junk $<date_of_birth::::20>$', 3181 # u'junk $<date_of_birth::::>$', 3182 'junk $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::>>>$ junk', 3183 'junk $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::250>>>$ junk', 3184 'junk $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::3-4>>>$ junk', 3185 3186 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::->>>$ junk', 3187 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::3->>>$ junk', 3188 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::-4>>>$ should fail', 3189 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::should_fail>>>$ junk', 3190 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::should_fail->>>$ junk', 3191 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::-should_fail>>>$ junk', 3192 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::should_fail-4>>>$ junk', 3193 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::3-should_fail>>>$ junk', 3194 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::should_fail-should_fail>>>$ junk', 3195 ] 3196 3197 tests = [ 3198 'junk $<<<should pass::template::>>>$ junk', 3199 'junk $<<<should pass::template::10>>>$ junk', 3200 'junk $<<<should pass::template::10-20>>>$ junk', 3201 'junk $<<<should pass::template $<<dummy::template 2::10>>$::>>>$ junk', 3202 'junk $<<<should pass::template $<dummy::template 2::10>$::>>>$ junk', 3203 3204 'junk $<<<should pass::template::>>>$ junk $<<<should pass 2::template 2::>>>$ junk', 3205 'junk $<<<should pass::template::>>>$ junk $<<should pass 2::template 2::>>$ junk', 3206 'junk $<<<should pass::template::>>>$ junk $<should pass 2::template 2::>$ junk', 3207 3208 'junk $<<<should fail::template $<<<dummy::template 2::10>>>$::>>>$ junk', 3209 3210 'junk $<<<should fail::template::10->>>$ junk', 3211 'junk $<<<should fail::template::10->>>$ junk', 3212 'junk $<<<should fail::template::10->>>$ junk', 3213 'junk $<<<should fail::template::10->>>$ junk', 3214 'junk $<first_pass::junk $<<<3rd_pass::template::20>>>$ junk::8-10>$ junk' 3215 ] 3216 3217 #print "testing placeholder regex:", first_pass_placeholder_regex 3218 ##print "testing placeholder regex:", second_pass_placeholder_regex 3219 ##print "testing placeholder regex:", third_pass_placeholder_regex 3220 #print "" 3221 #for t in tests: 3222 # print 'line: "%s"' % t 3223 # phs = regex.findall(first_pass_placeholder_regex, t, regex.IGNORECASE) 3224 # #phs = regex.findall(second_pass_placeholder_regex, t, regex.IGNORECASE) 3225 # #phs = regex.findall(third_pass_placeholder_regex, t, regex.IGNORECASE) 3226 # print " %s placeholders:" % len(phs) 3227 # for p in phs: 3228 # print ' => ', p 3229 # print " " 3230 3231 all_tests = { 3232 first_pass_placeholder_regex: [ 3233 # different lengths/regions 3234 ('junk $<first_level::template::>$ junk', ['$<first_level::template::>$']), 3235 ('junk $<first_level::template::10>$ junk', ['$<first_level::template::10>$']), 3236 ('junk $<first_level::template::10-12>$ junk', ['$<first_level::template::10-12>$']), 3237 3238 # inside is other-level: 3239 ('junk $<first_level::$<<insert::insert_template::0>>$::10-12>$ junk', ['$<first_level::$<<insert::insert_template::0>>$::10-12>$']), 3240 ('junk $<first_level::$<<<insert::insert_template::0>>>$::10-12>$ junk', ['$<first_level::$<<<insert::insert_template::0>>>$::10-12>$']), 3241 3242 # outside is other-level: 3243 ('junk $<<second_level::$<insert::insert_template::0>$::10-12>>$ junk', ['$<insert::insert_template::0>$']), 3244 ('junk $<<<third_level::$<insert::insert_template::0>$::10-12>>>$ junk', ['$<insert::insert_template::0>$']), 3245 3246 # other level on same line 3247 ('junk $<first_level 1::template 1::>$ junk $<<second_level 2::template 2::>>$ junk', ['$<first_level 1::template 1::>$']), 3248 ('junk $<first_level 1::template 1::>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<first_level 1::template 1::>$']), 3249 3250 # this should produce 2 matches 3251 ('junk $<first_level 1::template 1::>$ junk $<first_level 2::template 2::>$ junk', ['$<first_level 1::template 1::>$', '$<first_level 2::template 2::>$']), 3252 3253 # this will produce a mismatch, due to illegal nesting of same-level placeholders 3254 ('returns illegal match: junk $<first_level::$<insert::insert_template::0>$::10-12>$ junk', ['$<first_level::$<insert::insert_template::0>$::10-12>$']), 3255 ], 3256 second_pass_placeholder_regex: [ 3257 # different lengths/regions 3258 ('junk $<<second_level::template::>>$ junk', ['$<<second_level::template::>>$']), 3259 ('junk $<<second_level::template::10>>$ junk', ['$<<second_level::template::10>>$']), 3260 ('junk $<<second_level::template::10-12>>$ junk', ['$<<second_level::template::10-12>>$']), 3261 3262 # inside is other-level: 3263 ('junk $<<second_level::$<insert::insert_template::0>$::10-12>>$ junk', ['$<<second_level::$<insert::insert_template::0>$::10-12>>$']), 3264 ('junk $<<second_level::$<<<insert::insert_template::0>>>$::10-12>>$ junk', ['$<<second_level::$<<<insert::insert_template::0>>>$::10-12>>$']), 3265 3266 # outside is other-level: 3267 ('junk $<first_level::$<<insert::insert_template::0>>$::10-12>$ junk', ['$<<insert::insert_template::0>>$']), 3268 ('junk $<<<third_level::$<<insert::insert_template::0>>$::10-12>>>$ junk', ['$<<insert::insert_template::0>>$']), 3269 3270 # other level on same line 3271 ('junk $<first_level 1::template 1::>$ junk $<<second_level 2::template 2::>>$ junk', ['$<<second_level 2::template 2::>>$']), 3272 ('junk $<<second_level 1::template 1::>>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<<second_level 1::template 1::>>$']), 3273 3274 # this should produce 2 matches 3275 ('junk $<<second_level 1::template 1::>>$ junk $<<second_level 2::template 2::>>$ junk', ['$<<second_level 1::template 1::>>$', '$<<second_level 2::template 2::>>$']), 3276 3277 # this will produce a mismatch, due to illegal nesting of same-level placeholders 3278 ('returns illegal match: junk $<<second_level::$<<insert::insert_template::0>>$::10-12>>$ junk', ['$<<second_level::$<<insert::insert_template::0>>$::10-12>>$']), 3279 3280 ], 3281 third_pass_placeholder_regex: [ 3282 # different lengths/regions 3283 ('junk $<<<third_level::template::>>>$ junk', ['$<<<third_level::template::>>>$']), 3284 ('junk $<<<third_level::template::10>>>$ junk', ['$<<<third_level::template::10>>>$']), 3285 ('junk $<<<third_level::template::10-12>>>$ junk', ['$<<<third_level::template::10-12>>>$']), 3286 3287 # inside is other-level: 3288 ('junk $<<<third_level::$<<insert::insert_template::0>>$::10-12>>>$ junk', ['$<<<third_level::$<<insert::insert_template::0>>$::10-12>>>$']), 3289 ('junk $<<<third_level::$<insert::insert_template::0>$::10-12>>>$ junk', ['$<<<third_level::$<insert::insert_template::0>$::10-12>>>$']), 3290 3291 # outside is other-level: 3292 ('junk $<<second_level::$<<<insert::insert_template::0>>>$::10-12>>$ junk', ['$<<<insert::insert_template::0>>>$']), 3293 ('junk $<first_level::$<<<insert::insert_template::0>>>$::10-12>$ junk', ['$<<<insert::insert_template::0>>>$']), 3294 3295 # other level on same line 3296 ('junk $<first_level 1::template 1::>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<<<third_level 2::template 2::>>>$']), 3297 ('junk $<<second_level 1::template 1::>>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<<<third_level 2::template 2::>>>$']), 3298 3299 # this will produce a mismatch, due to illegal nesting of same-level placeholders 3300 ('returns illegal match: junk $<<<third_level::$<<<insert::insert_template::0>>>$::10-12>>>$ junk', ['$<<<third_level::$<<<insert::insert_template::0>>>$::10-12>>>$']), 3301 ] 3302 } 3303 3304 for pattern in [first_pass_placeholder_regex, second_pass_placeholder_regex, third_pass_placeholder_regex]: 3305 print("") 3306 print("-----------------------------") 3307 print("regex:", pattern) 3308 tests = all_tests[pattern] 3309 for t in tests: 3310 line, expected_results = t 3311 phs = regex.findall(pattern, line, regex.IGNORECASE) 3312 if len(phs) > 0: 3313 if phs == expected_results: 3314 continue 3315 3316 print("") 3317 print("failed") 3318 print("line:", line) 3319 3320 if len(phs) == 0: 3321 print("no match") 3322 continue 3323 3324 if len(phs) > 1: 3325 print("several matches") 3326 for r in expected_results: 3327 print("expected:", r) 3328 for p in phs: 3329 print("found:", p) 3330 continue 3331 3332 print("unexpected match") 3333 print("expected:", expected_results) 3334 print("found: ", phs)
3335 3336 #--------------------------------------------------------
3337 - def test_placeholder():
3338 3339 phs = [ 3340 #u'emr_journal::soapu //%(clin_when)s %(modified_by)s %(soap_cat)s %(narrative)s//1000 days::', 3341 #u'free_text::placeholder test//preset::9999', 3342 #u'soap_for_encounters:://::9999', 3343 #u'soap_p', 3344 #u'encounter_list::%(started)s: %(assessment_of_encounter)s::30', 3345 #u'patient_comm::homephone::1234', 3346 #u'$<patient_address::work::1234>$', 3347 #u'adr_region::home::1234', 3348 #u'adr_country::fehlt::1234', 3349 #u'adr_subunit::fehlt::1234', 3350 #u'adr_suburb::fehlt-auch::1234', 3351 #u'external_id::Starfleet Serial Number//Star Fleet Central Staff Office::1234', 3352 #u'primary_praxis_provider', 3353 #u'current_provider::::3-5', 3354 #u'current_provider_external_id::Starfleet Serial Number//Star Fleet Central Staff Office::1234', 3355 #u'current_provider_external_id::LANR//LÄK::1234' 3356 #u'$<current_provider_external_id::KV-LANR//KV::1234>$' 3357 #u'primary_praxis_provider_external_id::LANR//LÄK::1234' 3358 #u'form_name_long::::1234', 3359 #u'form_name_long::::5', 3360 #u'form_name_long::::', 3361 #u'form_version::::5', 3362 #u'$<current_meds::\item %(product)s %(preparation)s (%(substance)s) from %(started)s for %(duration)s as %(schedule)s until %(discontinued)s\\n::250>$', 3363 #u'$<vaccination_history::%(date_given)s: %(vaccine)s [%(batch_no)s] %(l10n_indications)s::250>$', 3364 #u'$<date_of_birth::%Y %B %d::20>$', 3365 #u'$<date_of_birth::%Y %B %d::>$', 3366 #u'$<date_of_birth::::20>$', 3367 #u'$<date_of_birth::::>$', 3368 #u'$<patient_tags::Tag "%(l10n_description)s": %(comment)s//\\n- ::250>$', 3369 #u'$<PHX::%(description)s\n side: %(laterality)s, active: %(is_active)s, relevant: %(clinically_relevant)s, caused death: %(is_cause_of_death)s//\n//%Y %B %d//latex::250>$', 3370 #u'$<patient_photo::\includegraphics[width=60mm]{%s}//image/png//.png::250>$', 3371 #u'$<data_snippet::binary_test_snippet//path=<%s>//image/png//.png::250>$', 3372 #u'$<data_snippet::autograph-LMcC//path=<%s>//image/jpg//.jpg::250>$', 3373 #u'$<current_meds::%s ($<lastname::::50>$)//select::>$', 3374 #u'$<current_meds::%s//select::>$', 3375 #u'$<soap_by_issue::soapu //%Y %b %d//%(narrative)s::1000>$', 3376 #u'$<soap_by_episode::soapu //%Y %b %d//%(narrative)s::1000>$', 3377 #u'$<documents::select//description//document %(clin_when)s: %(l10n_type)s// file: %(fullpath)s (<some path>/%(name)s)//~/gnumed/export/::>$', 3378 #u'$<soap::soapu //%s::9999>$', 3379 #u'$<soap::soapu //%(soap_cat)s: %(date)s | %(provider)s | %(narrative)s::9999>$' 3380 #u'$<test_results:://%c::>$' 3381 #u'$<test_results::%(unified_abbrev)s: %(unified_val)s %(val_unit)s//%c::>$' 3382 #u'$<reminders:://::>$' 3383 #u'$<current_meds_for_rx::%(product)s (%(contains)s): dispense %(amount2dispense)s ::>$' 3384 #u'$<praxis::%(branch)s (%(praxis)s)::>$' 3385 #u'$<praxis_address::::120>$' 3386 #u'$<praxis_id::::120>$' 3387 #u'$<gen_adr_street::Street = %s//Wählen Sie die Empfängeradresse !::120>$', u'$<gen_adr_location::Ort = %s::120>$', u'$<gen_adr_country::::120>$' 3388 3389 #u'$<receiver_name::%s::120>$', 3390 #u'$<receiver_street::%s//a::120>$', 3391 #u'$<receiver_number:: %s//a::120>$', 3392 #u'$<receiver_subunit:: %s::120>$', 3393 #u'$<receiver_postcode::%s//b::120>$', 3394 #u'$<receiver_location:: %s::120>$', 3395 #u'$<receiver_country::, %s::120>$', 3396 #u'$<external_care::%(issue)s: %(provider)s of %(unit)s@%(organization)s (%(comment)s)::1024>$', 3397 #u'$<url_escape::hello world ü::>$', 3398 #u'$<substance_abuse::%(substance)s (%(harmful_use_type)s) last=%(last_checked_when)s stop=%(discontinued)s // %(notes)s::>$', 3399 #u'bill_adr_region::region %s::1234', 3400 #u'bill_adr_country::%s::1234', 3401 #u'bill_adr_subunit::subunit: %s::1234', 3402 #u'bill_adr_suburb::-> %s::1234', 3403 #u'bill_adr_street::::1234', 3404 #u'bill_adr_number::%s::1234', 3405 #u'$<diagnoses::\listitem %s::>$' 3406 u'$<patient_mcf::fmt=txt//card=%s::>$', 3407 u'$<patient_mcf::fmt=mcf//mcf=%s::>$', 3408 u'$<patient_mcf::fmt=qr//png=%s::>$' 3409 ] 3410 3411 handler = gmPlaceholderHandler() 3412 handler.debug = True 3413 3414 gmStaff.set_current_provider_to_logged_on_user() 3415 gmPraxisWidgets.set_active_praxis_branch(no_parent = True) 3416 pat = gmPersonSearch.ask_for_patient() 3417 if pat is None: 3418 return 3419 gmPatSearchWidgets.set_active_patient(patient = pat) 3420 3421 app = wx.PyWidgetTester(size = (200, 50)) 3422 #handler.set_placeholder('form_name_long', 'ein Testformular') 3423 for ph in phs: 3424 print(ph) 3425 print(" result:") 3426 print(' %s' % handler[ph])
3427 #handler.unset_placeholder('form_name_long') 3428 3429 #--------------------------------------------------------
3430 - def test():
3431 pat = gmPersonSearch.ask_for_patient() 3432 if pat is None: 3433 sys.exit() 3434 gmPerson.set_active_patient(patient = pat) 3435 from Gnumed.wxpython import gmMedicationWidgets 3436 gmMedicationWidgets.manage_substance_intakes()
3437 3438 #--------------------------------------------------------
3439 - def test_show_phs():
3440 show_placeholders()
3441 3442 #-------------------------------------------------------- 3443 3444 app = wx.App() 3445 3446 #test_placeholders() 3447 #test_new_variant_placeholders() 3448 #test_scripting() 3449 #test_placeholder_regex() 3450 #test() 3451 test_placeholder() 3452 #test_show_phs() 3453