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