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 template = u'%(title)s' 1689 elif data.strip() == u'': 1690 data = u'%(title)s' 1691 return self._get_variant_current_provider_name(data = data)
1692 1693 #--------------------------------------------------------
1694 - def _get_variant_current_provider_firstnames(self, data=None):
1695 if data is None: 1696 data = u'%(firstnames)s' 1697 elif data.strip() == u'': 1698 data = u'%(firstnames)s' 1699 return self._get_variant_current_provider_name(data = data)
1700 1701 #--------------------------------------------------------
1702 - def _get_variant_current_provider_lastnames(self, data=None):
1703 if data is None: 1704 data = u'%(lastnames)s' 1705 elif data.strip() == u'': 1706 data = u'%(lastnames)s' 1707 return self._get_variant_current_provider_name(data = data)
1708 1709 #--------------------------------------------------------
1710 - def _get_variant_current_provider_name(self, data=None):
1711 if data is None: 1712 return [_('template is missing')] 1713 if data.strip() == '': 1714 return [_('template is empty')] 1715 name = gmStaff.gmCurrentProvider().identity.get_active_name() 1716 parts = { 1717 'title': self._escape(gmTools.coalesce(name['title'], '')), 1718 'firstnames': self._escape(name['firstnames']), 1719 'lastnames': self._escape(name['lastnames']), 1720 'preferred': self._escape(gmTools.coalesce(name['preferred'], '')) 1721 } 1722 return data % parts
1723 1724 #--------------------------------------------------------
1726 data_parts = data.split(self.__args_divider) 1727 if len(data_parts) < 2: 1728 return self._escape('current provider external ID: template is missing') 1729 1730 id_type = data_parts[0].strip() 1731 if id_type == '': 1732 return self._escape('current provider external ID: type is missing') 1733 1734 issuer = data_parts[1].strip() 1735 if issuer == '': 1736 return self._escape('current provider external ID: issuer is missing') 1737 1738 prov = gmStaff.gmCurrentProvider() 1739 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer) 1740 1741 if len(ids) == 0: 1742 if self.debug: 1743 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer)) 1744 return '' 1745 1746 return self._escape(ids[0]['value'])
1747 1748 #--------------------------------------------------------
1749 - def _get_variant_primary_praxis_provider(self, data=None):
1750 prov = self.pat.primary_provider 1751 if prov is None: 1752 return self._get_variant_current_provider() 1753 1754 title = gmTools.coalesce ( 1755 prov['title'], 1756 gmPerson.map_gender2salutation(prov['gender']) 1757 ) 1758 1759 tmp = '%s %s. %s' % ( 1760 title, 1761 prov['firstnames'][:1], 1762 prov['lastnames'] 1763 ) 1764 return self._escape(tmp)
1765 1766 #--------------------------------------------------------
1768 data_parts = data.split(self.__args_divider) 1769 if len(data_parts) < 2: 1770 return self._escape('primary in-praxis provider external ID: template is missing') 1771 1772 id_type = data_parts[0].strip() 1773 if id_type == '': 1774 return self._escape('primary in-praxis provider external ID: type is missing') 1775 1776 issuer = data_parts[1].strip() 1777 if issuer == '': 1778 return self._escape('primary in-praxis provider external ID: issuer is missing') 1779 1780 prov = self.pat.primary_provider 1781 if prov is None: 1782 if self.debug: 1783 return self._escape(_('no primary in-praxis provider')) 1784 return '' 1785 1786 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer) 1787 1788 if len(ids) == 0: 1789 if self.debug: 1790 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer)) 1791 return '' 1792 1793 return self._escape(ids[0]['value'])
1794 1795 #--------------------------------------------------------
1796 - def _get_variant_external_id(self, data=''):
1797 data_parts = data.split(self.__args_divider) 1798 if len(data_parts) < 2: 1799 return self._escape('patient external ID: template is missing') 1800 1801 id_type = data_parts[0].strip() 1802 if id_type == '': 1803 return self._escape('patient external ID: type is missing') 1804 1805 issuer = data_parts[1].strip() 1806 if issuer == '': 1807 return self._escape('patient external ID: issuer is missing') 1808 1809 ids = self.pat.get_external_ids(id_type = id_type, issuer = issuer) 1810 1811 if len(ids) == 0: 1812 if self.debug: 1813 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer)) 1814 return '' 1815 1816 return self._escape(ids[0]['value'])
1817 1818 #--------------------------------------------------------
1819 - def _get_variant_allergy_state(self, data=None):
1820 allg_state = self.pat.emr.allergy_state 1821 1822 if allg_state['last_confirmed'] is None: 1823 date_confirmed = '' 1824 else: 1825 date_confirmed = ' (%s)' % gmDateTime.pydt_strftime ( 1826 allg_state['last_confirmed'], 1827 format = '%Y %B %d' 1828 ) 1829 1830 tmp = '%s%s' % ( 1831 allg_state.state_string, 1832 date_confirmed 1833 ) 1834 return self._escape(tmp)
1835 1836 #--------------------------------------------------------
1837 - def _get_variant_allergy_list(self, data=None):
1838 if data is None: 1839 return self._escape(_('template is missing')) 1840 1841 template, separator = data.split('//', 2) 1842 1843 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() ])
1844 1845 #--------------------------------------------------------
1846 - def _get_variant_allergies(self, data=None):
1847 1848 if data is None: 1849 return self._escape(_('template is missing')) 1850 1851 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() ])
1852 1853 #--------------------------------------------------------
1854 - def _get_variant_current_meds_AMTS_enhanced(self, data=None):
1855 return self._get_variant_current_meds_AMTS(data=data, strict=False)
1856 1857 #--------------------------------------------------------
1858 - def _get_variant_current_meds_AMTS(self, data=None, strict=True):
1859 1860 # select intakes 1861 emr = self.pat.emr 1862 from Gnumed.wxpython import gmMedicationWidgets 1863 intakes2export = gmMedicationWidgets.manage_substance_intakes(emr = emr) 1864 if intakes2export is None: 1865 return '' 1866 if len(intakes2export) == 0: 1867 return '' 1868 1869 # make them unique: 1870 unique_intakes = {} 1871 for intake in intakes2export: 1872 if intake['pk_drug_product'] is None: 1873 unique_intakes[intake['pk_substance']] = intake 1874 else: 1875 unique_intakes[intake['product']] = intake 1876 del intakes2export 1877 unique_intakes = unique_intakes.values() 1878 1879 # create data files / datamatrix code files 1880 self.__create_amts_datamatrix_files(intakes = unique_intakes) 1881 1882 # create AMTS-LaTeX per intake 1883 intake_as_latex_rows = [] 1884 for intake in unique_intakes: 1885 intake_as_latex_rows.append(intake._get_as_amts_latex(strict = strict)) 1886 del unique_intakes 1887 1888 # append allergy information 1889 # - state 1890 intake_as_latex_rows.extend(emr.allergy_state._get_as_amts_latex(strict = strict)) 1891 # - allergies 1892 for allg in emr.get_allergies(): 1893 intake_as_latex_rows.append(allg._get_as_amts_latex(strict = strict)) 1894 1895 # insert \newpage after each group of 15 rows 1896 table_rows = intake_as_latex_rows[:15] 1897 if len(intake_as_latex_rows) > 15: 1898 table_rows.append('\\newpage') 1899 table_rows.extend(intake_as_latex_rows[15:30]) 1900 if len(intake_as_latex_rows) > 30: 1901 table_rows.append('\\newpage') 1902 table_rows.extend(intake_as_latex_rows[30:45]) 1903 1904 if strict: 1905 return '\n'.join(table_rows) 1906 1907 # allow two more pages in enhanced mode 1908 if len(intake_as_latex_rows) > 45: 1909 table_rows.append('\\newpage') 1910 table_rows.extend(intake_as_latex_rows[30:45]) 1911 1912 if len(intake_as_latex_rows) > 60: 1913 table_rows.append('\\newpage') 1914 table_rows.extend(intake_as_latex_rows[30:45]) 1915 1916 return '\n'.join(table_rows)
1917 1918 #--------------------------------------------------------
1919 - def __create_amts_datamatrix_files(self, intakes=None):
1920 1921 # setup dummy files 1922 for idx in [1,2,3]: 1923 self.set_placeholder(key = 'amts_data_file_%s' % idx, value = './missing-file.txt', known_only = False) 1924 self.set_placeholder(key = 'amts_png_file_%s' % idx, value = './missing-file.png', known_only = False) 1925 self.set_placeholder(key = 'amts_png_file_current_page', value = './missing-file-current-page.png', known_only = False) 1926 self.set_placeholder(key = 'amts_png_file_utf8', value = './missing-file-utf8.png', known_only = False) 1927 self.set_placeholder(key = 'amts_data_file_utf8', value = './missing-file-utf8.txt', known_only = False) 1928 1929 # find processor 1930 found, dmtx_creator = gmShellAPI.detect_external_binary(binary = 'gm-create_datamatrix') 1931 _log.debug(dmtx_creator) 1932 if not found: 1933 _log.error('gm-create_datamatrix(.bat/.exe) not found') 1934 return 1935 1936 png_dir = gmTools.mk_sandbox_dir() 1937 _log.debug('sandboxing AMTS datamatrix PNGs in: %s', png_dir) 1938 1939 from Gnumed.business import gmForms 1940 1941 # generate GNUmed-enhanced non-conformant data file and datamatrix 1942 # for embedding (utf8, unabridged data fields) 1943 amts_data_template_def_file = gmMedication.generate_amts_data_template_definition_file(strict = False) 1944 _log.debug('amts data template definition file: %s', amts_data_template_def_file) 1945 form = gmForms.cTextForm(template_file = amts_data_template_def_file) 1946 # <S>ection with intakes</S> 1947 amts_sections = '<S>%s</S>' % ''.join ([ 1948 i._get_as_amts_data(strict = False) for i in intakes 1949 ]) 1950 # <S>ection with allergy data</S> 1951 emr = self.pat.emr 1952 amts_sections += emr.allergy_state._get_as_amts_data(strict = False) % ''.join ([ 1953 a._get_as_amts_data(strict = False) for a in emr.get_allergies() 1954 ]) 1955 self.set_placeholder(key = 'amts_intakes_as_data_enhanced', value = amts_sections, known_only = False) 1956 # self.set_placeholder(key = u'amts_check_symbol', value = gmMedication.calculate_amts_data_check_symbol(intakes = intakes), known_only = False) 1957 self.set_placeholder(key = 'amts_total_pages', value = '1', known_only = False) 1958 success = form.substitute_placeholders(data_source = self) 1959 self.unset_placeholder(key = 'amts_intakes_as_data_enhanced') 1960 # self.unset_placeholder(key = u'amts_check_symbol') 1961 self.unset_placeholder(key = 'amts_total_pages') 1962 if not success: 1963 _log.error('cannot substitute into amts data file form template') 1964 return 1965 data_file = form.re_editable_filenames[0] 1966 png_file = os.path.join(png_dir, 'gm4amts-datamatrix-utf8.png') 1967 cmd = '%s %s %s' % (dmtx_creator, data_file, png_file) 1968 success = gmShellAPI.run_command_in_shell(command = cmd, blocking = True) 1969 if not success: 1970 _log.error('error running [%s]' % cmd) 1971 return 1972 self.set_placeholder(key = 'amts_data_file_utf8', value = data_file, known_only = False) 1973 self.set_placeholder(key = 'amts_png_file_utf8', value = png_file, known_only = False) 1974 1975 # generate conformant per-page files: 1976 total_pages = (len(intakes) / 15.0) 1977 if total_pages > int(total_pages): 1978 total_pages += 1 1979 total_pages = int(total_pages) 1980 _log.debug('total pages: %s', total_pages) 1981 1982 png_file_base = os.path.join(png_dir, 'gm4amts-datamatrix-page-') 1983 for this_page in range(1,total_pages+1): 1984 intakes_this_page = intakes[(this_page-1)*15:this_page*15] 1985 amts_data_template_def_file = gmMedication.generate_amts_data_template_definition_file(strict = True) 1986 _log.debug('amts data template definition file: %s', amts_data_template_def_file) 1987 form = gmForms.cTextForm(template_file = amts_data_template_def_file) 1988 # <S>ection with intakes</S> 1989 amts_sections = '<S>%s</S>' % ''.join ([ 1990 i._get_as_amts_data(strict = False) for i in intakes_this_page 1991 ]) 1992 if this_page == total_pages: 1993 # <S>ection with allergy data</S> 1994 emr = self.pat.emr 1995 amts_sections += emr.allergy_state._get_as_amts_data(strict = False) % ''.join ([ 1996 a._get_as_amts_data(strict = False) for a in emr.get_allergies() 1997 ]) 1998 self.set_placeholder(key = 'amts_intakes_as_data', value = amts_sections, known_only = False) 1999 # self.set_placeholder(key = u'amts_check_symbol', value = gmMedication.calculate_amts_data_check_symbol(intakes = intakes_this_page), known_only = False) 2000 if total_pages == 1: 2001 pg_idx = '' 2002 else: 2003 pg_idx = '%s' % this_page 2004 self.set_placeholder(key = 'amts_page_idx', value = pg_idx, known_only = False) 2005 self.set_placeholder(key = 'amts_total_pages', value = '%s' % total_pages, known_only = False) 2006 success = form.substitute_placeholders(data_source = self) 2007 self.unset_placeholder(key = 'amts_intakes_as_data') 2008 # self.unset_placeholder(key = u'amts_check_symbol') 2009 self.unset_placeholder(key = 'amts_page_idx') 2010 self.unset_placeholder(key = 'amts_total_pages') 2011 if not success: 2012 _log.error('cannot substitute into amts data file form template') 2013 return 2014 2015 data_file = form.re_editable_filenames[0] 2016 png_file = '%s%s.png' % (png_file_base, this_page) 2017 latin1_data_file = gmTools.recode_file ( 2018 source_file = data_file, 2019 source_encoding = 'utf8', 2020 target_encoding = 'latin1', 2021 base_dir = os.path.split(data_file)[0] 2022 ) 2023 cmd = '%s %s %s' % (dmtx_creator, latin1_data_file, png_file) 2024 success = gmShellAPI.run_command_in_shell(command = cmd, blocking = True) 2025 if not success: 2026 _log.error('error running [%s]' % cmd) 2027 return 2028 2029 # cache file names for later use in \embedfile 2030 self.set_placeholder(key = 'amts_data_file_%s' % this_page, value = latin1_data_file, known_only = False) 2031 self.set_placeholder(key = 'amts_png_file_%s' % this_page, value = png_file, known_only = False) 2032 2033 self.set_placeholder(key = 'amts_png_file_current_page', value = png_file_base + '\\thepage', known_only = False)
2034 2035 #--------------------------------------------------------
2036 - def _get_variant_current_meds_for_rx(self, data=None):
2037 if data is None: 2038 return self._escape(_('current_meds_for_rx: template is missing')) 2039 2040 emr = self.pat.emr 2041 from Gnumed.wxpython import gmMedicationWidgets 2042 current_meds = gmMedicationWidgets.manage_substance_intakes(emr = emr) 2043 if current_meds is None: 2044 return '' 2045 2046 intakes2show = {} 2047 for intake in current_meds: 2048 fields_dict = intake.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) 2049 fields_dict['medically_formatted_start'] = self._escape(intake.medically_formatted_start) 2050 if intake['pk_drug_product'] is None: 2051 fields_dict['product'] = self._escape(_('generic %s') % fields_dict['substance']) 2052 fields_dict['contains'] = self._escape('%s %s%s' % (fields_dict['substance'], fields_dict['amount'], fields_dict['unit'])) 2053 intakes2show[fields_dict['product']] = fields_dict 2054 else: 2055 comps = [ c.split('::') for c in intake.containing_drug['components'] ] 2056 fields_dict['contains'] = self._escape('; '.join([ '%s %s%s' % (c[0], c[1], c[2]) for c in comps ])) 2057 intakes2show[intake['product']] = fields_dict # this will make multi-component drugs unique 2058 2059 intakes2dispense = {} 2060 for product, intake in intakes2show.items(): 2061 msg = _('Dispense how much/many of "%(product)s (%(contains)s)" ?') % intake 2062 amount2dispense = wx.GetTextFromUser(msg, _('Amount to dispense ?')) 2063 if amount2dispense == '': 2064 continue 2065 intake['amount2dispense'] = amount2dispense 2066 intakes2dispense[product] = intake 2067 2068 return '\n'.join([ data % intake for intake in intakes2dispense.values() ])
2069 2070 #--------------------------------------------------------
2071 - def _get_variant_substance_abuse(self, data=None):
2072 if data is None: 2073 return self._escape(_('template is missing')) 2074 template = data 2075 from Gnumed.wxpython import gmHabitWidgets 2076 abuses = gmHabitWidgets.manage_substance_abuse(patient = self.pat) 2077 if abuses is None: 2078 return '' 2079 lines = [] 2080 for a in abuses: 2081 fields = a.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) 2082 fields['harmful_use_type'] = a.harmful_use_type_string 2083 lines.append(template % fields) 2084 return '\n'.join(lines)
2085 2086 #--------------------------------------------------------
2087 - def _get_variant_current_meds(self, data=None):
2088 2089 if data is None: 2090 return self._escape(_('template is missing')) 2091 2092 parts = data.split(self.__args_divider) 2093 template = parts[0] 2094 ask_user = False 2095 if len(parts) > 1: 2096 ask_user = (parts[1] == 'select') 2097 2098 emr = self.pat.emr 2099 if ask_user: 2100 from Gnumed.wxpython import gmMedicationWidgets 2101 current_meds = gmMedicationWidgets.manage_substance_intakes(emr = emr) 2102 if current_meds is None: 2103 return '' 2104 else: 2105 current_meds = emr.get_current_medications ( 2106 include_inactive = False, 2107 include_unapproved = True, 2108 order_by = 'product, substance' 2109 ) 2110 if len(current_meds) == 0: 2111 return '' 2112 2113 lines = [] 2114 for m in current_meds: 2115 data = m.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) 2116 data['medically_formatted_start'] = self._escape(m.medically_formatted_start) 2117 lines.append(template % data) 2118 2119 return '\n'.join(lines)
2120 #--------------------------------------------------------
2121 - def _get_variant_current_meds_table(self, data=None):
2122 return gmMedication.format_substance_intake ( 2123 emr = self.pat.emr, 2124 output_format = self.__esc_style, 2125 table_type = 'by-product' 2126 )
2127 #--------------------------------------------------------
2128 - def _get_variant_current_meds_notes(self, data=None):
2129 return gmMedication.format_substance_intake_notes ( 2130 emr = self.pat.emr, 2131 output_format = self.__esc_style, 2132 table_type = 'by-product' 2133 )
2134 #--------------------------------------------------------
2135 - def _get_variant_lab_table(self, data=None):
2136 return gmPathLab.format_test_results ( 2137 results = self.pat.emr.get_test_results_by_date(), 2138 output_format = self.__esc_style 2139 )
2140 #--------------------------------------------------------
2141 - def _get_variant_test_results(self, data=None):
2142 2143 template = '' 2144 date_format = '%Y %b %d %H:%M' 2145 separator = '\n' 2146 2147 options = data.split(self.__args_divider) 2148 try: 2149 template = options[0].strip() 2150 date_format = options[1] 2151 separator = options[2] 2152 except IndexError: 2153 pass 2154 2155 if date_format.strip() == '': 2156 date_format = '%Y %b %d %H:%M' 2157 if separator.strip() == '': 2158 separator = '\n' 2159 2160 #results = gmMeasurementWidgets.manage_measurements(single_selection = False, emr = self.pat.emr) 2161 from Gnumed.wxpython.gmMeasurementWidgets import manage_measurements 2162 results = manage_measurements(single_selection = False, emr = self.pat.emr) 2163 if results is None: 2164 if self.debug: 2165 return self._escape(_('no results for this patient (available or selected)')) 2166 return '' 2167 2168 if template == '': 2169 return (separator + separator).join([ self._escape(r.format(date_format = date_format)) for r in results ]) 2170 2171 return separator.join([ template % r.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for r in results ])
2172 #--------------------------------------------------------
2173 - def _get_variant_latest_vaccs_table(self, data=None):
2174 return gmVaccination.format_latest_vaccinations ( 2175 output_format = self.__esc_style, 2176 emr = self.pat.emr 2177 )
2178 #--------------------------------------------------------
2179 - def _get_variant_vaccination_history(self, data=None):
2180 options = data.split(self.__args_divider) 2181 template = options[0] 2182 if len(options) > 1: 2183 date_format = options[1] 2184 else: 2185 date_format = '%Y %b %d' 2186 2187 vaccs = self.pat.emr.get_vaccinations(order_by = 'date_given DESC, vaccine') 2188 2189 return '\n'.join([ template % v.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for v in vaccs ])
2190 #--------------------------------------------------------
2191 - def _get_variant_PHX(self, data=None):
2192 2193 if data is None: 2194 if self.debug: 2195 _log.error('PHX: missing placeholder arguments') 2196 return self._escape(_('PHX: Invalid placeholder options.')) 2197 return '' 2198 2199 _log.debug('arguments: %s', data) 2200 2201 data_parts = data.split(self.__args_divider) 2202 template = '%s' 2203 separator = '\n' 2204 date_format = '%Y %b %d' 2205 try: 2206 template = data_parts[0] 2207 separator = data_parts[1] 2208 date_format = data_parts[2] 2209 except IndexError: 2210 pass 2211 2212 phxs = gmEMRStructWidgets.select_health_issues(emr = self.pat.emr) 2213 if phxs is None: 2214 if self.debug: 2215 return self._escape(_('no PHX for this patient (available or selected)')) 2216 return '' 2217 2218 return separator.join ([ 2219 template % phx.fields_as_dict ( 2220 date_format = date_format, 2221 escape_style = self.__esc_style, 2222 bool_strings = (self._escape(_('yes')), self._escape(_('no'))) 2223 ) for phx in phxs 2224 ])
2225 2226 #--------------------------------------------------------
2227 - def _get_variant_problems(self, data=None):
2228 2229 if data is None: 2230 return self._escape(_('template is missing')) 2231 probs = self.pat.emr.get_problems() 2232 return '\n'.join([ data % p.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style) for p in probs ])
2233 2234 #--------------------------------------------------------
2235 - def _get_variant_diagnoses(self, data=None):
2236 2237 if data is None: 2238 return self._escape(_('template is missing')) 2239 template = data 2240 dxs = self.pat.emr.candidate_diagnoses 2241 if len(dxs) == 0: 2242 _log.debug('no diagnoses available') 2243 return '' 2244 selected = gmListWidgets.get_choices_from_list ( 2245 msg = _('Select the relevant diagnoses:'), 2246 caption = _('Diagnosis selection'), 2247 columns = [ _('Diagnosis'), _('Marked confidential'), _('Certainty'), _('Source') ], 2248 choices = [[ 2249 dx['diagnosis'], 2250 gmTools.bool2subst(dx['explicitely_confidential'], _('yes'), _('no'), _('unknown')), 2251 gmTools.coalesce(dx['diagnostic_certainty_classification'], ''), 2252 dx['source'] 2253 ] for dx in dxs 2254 ], 2255 data = dxs, 2256 single_selection = False, 2257 can_return_empty = True 2258 ) 2259 if selected is None: 2260 _log.debug('user did not select any diagnoses') 2261 return '' 2262 if len(selected) == 0: 2263 _log.debug('user did not select any diagnoses') 2264 return '' 2265 #return template % {'diagnosis': u'', 'diagnostic_certainty_classification': u''} 2266 return '\n'.join(template % self._escape_dict(dx, none_string = '?', bool_strings = [_('yes'), _('no')]) for dx in selected)
2267 2268 #--------------------------------------------------------
2269 - def _get_variant_today(self, data='%Y %b %d'):
2270 return self._escape(gmDateTime.pydt_now_here().strftime(data))
2271 2272 #--------------------------------------------------------
2273 - def _get_variant_tex_escape(self, data=None):
2275 2276 #--------------------------------------------------------
2277 - def _get_variant_url_escape(self, data=None):
2278 return self._escape(urllib.parse.quote(data.encode('utf8')))
2279 2280 #--------------------------------------------------------
2281 - def _get_variant_text_snippet(self, data=None):
2282 data_parts = data.split(self.__args_divider) 2283 keyword = data_parts[0] 2284 template = '%s' 2285 if len(data_parts) > 1: 2286 template = data_parts[1] 2287 2288 expansion = gmKeywordExpansionWidgets.expand_keyword(keyword = keyword, show_list_if_needed = True) 2289 2290 if expansion is None: 2291 if self.debug: 2292 return self._escape(_('no textual expansion found for keyword <%s>') % keyword) 2293 return '' 2294 2295 #return template % self._escape(expansion) 2296 return template % expansion
2297 2298 #--------------------------------------------------------
2299 - def _get_variant_data_snippet(self, data=None):
2300 parts = data.split(self.__args_divider) 2301 keyword = parts[0] 2302 template = '%s' 2303 target_mime = None 2304 target_ext = None 2305 if len(parts) > 1: 2306 template = parts[1] 2307 if len(parts) > 2: 2308 target_mime = parts[2].strip() 2309 if len(parts) > 3: 2310 target_ext = parts[3].strip() 2311 if target_ext is None: 2312 if target_mime is not None: 2313 target_ext = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime) 2314 2315 expansion = gmKeywordExpansion.get_expansion ( 2316 keyword = keyword, 2317 textual_only = False, 2318 binary_only = True 2319 ) 2320 if expansion is None: 2321 if self.debug: 2322 return self._escape(_('no binary expansion found for keyword <%s>') % keyword) 2323 return '' 2324 2325 filename = expansion.save_to_file() 2326 if filename is None: 2327 if self.debug: 2328 return self._escape(_('cannot export data of binary expansion keyword <%s>') % keyword) 2329 return '' 2330 2331 if expansion['is_encrypted']: 2332 pwd = wx.GetPasswordFromUser ( 2333 message = _('Enter your GnuPG passphrase for decryption of [%s]') % expansion['keyword'], 2334 caption = _('GnuPG passphrase prompt'), 2335 default_value = '' 2336 ) 2337 filename = gmTools.gpg_decrypt_file(filename = filename, passphrase = pwd) 2338 if filename is None: 2339 if self.debug: 2340 return self._escape(_('cannot decrypt data of binary expansion keyword <%s>') % keyword) 2341 return '' 2342 2343 target_fname = gmTools.get_unique_filename ( 2344 prefix = '%s-converted-' % os.path.splitext(filename)[0], 2345 suffix = target_ext 2346 ) 2347 if not gmMimeLib.convert_file(filename = filename, target_mime = target_mime, target_filename = target_fname): 2348 if self.debug: 2349 return self._escape(_('cannot convert data of binary expansion keyword <%s>') % keyword) 2350 # hoping that the target can cope: 2351 return template % filename 2352 2353 return template % target_fname
2354 2355 #--------------------------------------------------------
2356 - def _get_variant_range_of(self, data=None):
2357 if data is None: 2358 return None 2359 # wrapper code already takes care of actually 2360 # selecting the range so all we need to do here 2361 # is to return the data itself 2362 return data
2363 2364 #--------------------------------------------------------
2365 - def _get_variant_if_not_empty(self, data=None):
2366 if data is None: 2367 return None 2368 2369 parts = data.split(self.__args_divider) 2370 if len(parts) < 3: 2371 return 'IF_NOT_EMPTY lacks <instead> definition' 2372 txt = parts[0] 2373 template = parts[1] 2374 instead = parts[2] 2375 2376 if txt.strip() == '': 2377 return instead 2378 if '%s' in template: 2379 return template % txt 2380 return template
2381 2382 #--------------------------------------------------------
2383 - def _get_variant_free_text(self, data=None):
2384 2385 if data is None: 2386 msg = _('generic text') 2387 cache_key = 'free_text::%s' % datetime.datetime.now() 2388 else: 2389 msg = data 2390 cache_key = 'free_text::%s' % msg 2391 2392 try: 2393 return self.__cache[cache_key] 2394 except KeyError: 2395 pass 2396 2397 dlg = gmGuiHelpers.cMultilineTextEntryDlg ( 2398 None, 2399 -1, 2400 title = _('Replacing <free_text> placeholder'), 2401 msg = _('Below you can enter free text.\n\n [%s]') % msg 2402 ) 2403 dlg.enable_user_formatting = True 2404 decision = dlg.ShowModal() 2405 text = dlg.value.strip() 2406 is_user_formatted = dlg.is_user_formatted 2407 dlg.Destroy() 2408 2409 if decision != wx.ID_SAVE: 2410 if self.debug: 2411 return self._escape(_('Text input cancelled by user.')) 2412 return self._escape('') 2413 2414 # user knows "best" 2415 if is_user_formatted: 2416 self.__cache[cache_key] = text 2417 return text 2418 2419 text = self._escape(text) 2420 self.__cache[cache_key] = text 2421 return text
2422 2423 #--------------------------------------------------------
2424 - def _get_variant_bill(self, data=None):
2425 try: 2426 bill = self.__cache['bill'] 2427 except KeyError: 2428 from Gnumed.wxpython import gmBillingWidgets 2429 bill = gmBillingWidgets.manage_bills(patient = self.pat) 2430 if bill is None: 2431 if self.debug: 2432 return self._escape(_('no bill selected')) 2433 return '' 2434 self.__cache['bill'] = bill 2435 2436 parts = data.split(self.__args_divider) 2437 template = parts[0] 2438 if len(parts) > 1: 2439 date_format = parts[1] 2440 else: 2441 date_format = '%Y %B %d' 2442 2443 return template % bill.fields_as_dict(date_format = date_format, escape_style = self.__esc_style)
2444 2445 #--------------------------------------------------------
2446 - def _get_variant_bill_item(self, data=None):
2447 try: 2448 bill = self.__cache['bill'] 2449 except KeyError: 2450 from Gnumed.wxpython import gmBillingWidgets 2451 bill = gmBillingWidgets.manage_bills(patient = self.pat) 2452 if bill is None: 2453 if self.debug: 2454 return self._escape(_('no bill selected')) 2455 return '' 2456 self.__cache['bill'] = bill 2457 2458 parts = data.split(self.__args_divider) 2459 template = parts[0] 2460 if len(parts) > 1: 2461 date_format = parts[1] 2462 else: 2463 date_format = '%Y %B %d' 2464 2465 return '\n'.join([ template % i.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for i in bill.bill_items ])
2466 2467 #--------------------------------------------------------
2468 - def __get_variant_bill_adr_part(self, data=None, part=None):
2469 try: 2470 bill = self.__cache['bill'] 2471 except KeyError: 2472 from Gnumed.wxpython import gmBillingWidgets 2473 bill = gmBillingWidgets.manage_bills(patient = self.pat) 2474 if bill is None: 2475 if self.debug: 2476 return self._escape(_('no bill selected')) 2477 return '' 2478 self.__cache['bill'] = bill 2479 self.__cache['bill-adr'] = bill.address 2480 2481 try: 2482 bill_adr = self.__cache['bill-adr'] 2483 except KeyError: 2484 bill_adr = bill.address 2485 self.__cache['bill-adr'] = bill_adr 2486 2487 if bill_adr is None: 2488 if self.debug: 2489 return self._escape(_('[%s] bill has no address') % part) 2490 return '' 2491 2492 if bill_adr[part] is None: 2493 return self._escape('') 2494 2495 if data is None: 2496 return self._escape(bill_adr[part]) 2497 2498 if data == '': 2499 return self._escape(bill_adr[part]) 2500 2501 return data % self._escape(bill_adr[part])
2502 2503 #--------------------------------------------------------
2504 - def _get_variant_bill_adr_street(self, data='?'):
2505 return self.__get_variant_bill_adr_part(data = data, part = 'street')
2506 2507 #--------------------------------------------------------
2508 - def _get_variant_bill_adr_number(self, data='?'):
2509 return self.__get_variant_bill_adr_part(data = data, part = 'number')
2510 2511 #--------------------------------------------------------
2512 - def _get_variant_bill_adr_subunit(self, data='?'):
2513 return self.__get_variant_bill_adr_part(data = data, part = 'subunit')
2514 #--------------------------------------------------------
2515 - def _get_variant_bill_adr_location(self, data='?'):
2516 return self.__get_variant_bill_adr_part(data = data, part = 'urb')
2517 2518 #--------------------------------------------------------
2519 - def _get_variant_bill_adr_suburb(self, data='?'):
2520 return self.__get_variant_bill_adr_part(data = data, part = 'suburb')
2521 2522 #--------------------------------------------------------
2523 - def _get_variant_bill_adr_postcode(self, data='?'):
2524 return self.__get_variant_bill_adr_part(data = data, part = 'postcode')
2525 2526 #--------------------------------------------------------
2527 - def _get_variant_bill_adr_region(self, data='?'):
2528 return self.__get_variant_bill_adr_part(data = data, part = 'l10n_region')
2529 2530 #--------------------------------------------------------
2531 - def _get_variant_bill_adr_country(self, data='?'):
2532 return self.__get_variant_bill_adr_part(data = data, part = 'l10n_country')
2533 2534 #-------------------------------------------------------- 2535 # internal helpers 2536 #--------------------------------------------------------
2537 - def _escape(self, text=None):
2538 if self.__esc_func is None: 2539 return text 2540 return self.__esc_func(text)
2541 2542 #--------------------------------------------------------
2543 - def _escape_dict(self, the_dict=None, date_format='%Y %b %d %H:%M', none_string='', bool_strings=None):
2544 if bool_strings is None: 2545 bools = {True: _('true'), False: _('false')} 2546 else: 2547 bools = {True: bool_strings[0], False: bool_strings[1]} 2548 data = {} 2549 for field in the_dict.keys(): 2550 # FIXME: harden against BYTEA fields 2551 #if type(self._payload[self._idx[field]]) == ... 2552 # data[field] = _('<%s bytes of binary data>') % len(self._payload[self._idx[field]]) 2553 # continue 2554 val = the_dict[field] 2555 if val is None: 2556 data[field] = none_string 2557 continue 2558 if isinstance(val, bool): 2559 data[field] = bools[val] 2560 continue 2561 if isinstance(val, datetime.datetime): 2562 data[field] = gmDateTime.pydt_strftime(val, format = date_format) 2563 if self.__esc_style in ['latex', 'tex']: 2564 data[field] = gmTools.tex_escape_string(data[field]) 2565 elif self.__esc_style in ['xetex', 'xelatex']: 2566 data[field] = gmTools.xetex_escape_string(data[field]) 2567 continue 2568 try: 2569 data[field] = str(val, encoding = 'utf8', errors = 'replace') 2570 except TypeError: 2571 try: 2572 data[field] = str(val) 2573 except (UnicodeDecodeError, TypeError): 2574 val = '%s' % str(val) 2575 data[field] = val.decode('utf8', 'replace') 2576 if self.__esc_style in ['latex', 'tex']: 2577 data[field] = gmTools.tex_escape_string(data[field]) 2578 elif self.__esc_style in ['xetex', 'xelatex']: 2579 data[field] = gmTools.xetex_escape_string(data[field]) 2580 return data
2581 2582 #---------------------------------------------------------------------
2583 -def test_placeholders():
2584 2585 _log.debug('testing for placeholders with pattern: %s', first_pass_placeholder_regex) 2586 2587 data_source = gmPlaceholderHandler() 2588 original_line = '' 2589 2590 while True: 2591 # get input from user 2592 line = wx.GetTextFromUser ( 2593 _('Enter some text containing a placeholder:'), 2594 _('Testing placeholders'), 2595 centre = True, 2596 default_value = original_line 2597 ) 2598 if line.strip() == '': 2599 break 2600 original_line = line 2601 # replace 2602 placeholders_in_line = regex.findall(first_pass_placeholder_regex, line, regex.IGNORECASE) 2603 if len(placeholders_in_line) == 0: 2604 continue 2605 for placeholder in placeholders_in_line: 2606 try: 2607 val = data_source[placeholder] 2608 except: 2609 val = _('error with placeholder [%s]') % placeholder 2610 if val is None: 2611 val = _('error with placeholder [%s]') % placeholder 2612 line = line.replace(placeholder, val) 2613 # show 2614 msg = _( 2615 'Input: %s\n' 2616 '\n' 2617 'Output:\n' 2618 '%s' 2619 ) % ( 2620 original_line, 2621 line 2622 ) 2623 gmGuiHelpers.gm_show_info ( 2624 title = _('Testing placeholders'), 2625 info = msg 2626 )
2627 2628 #=====================================================================
2629 -class cMacroPrimitives:
2630 """Functions a macro can legally use. 2631 2632 An instance of this class is passed to the GNUmed scripting 2633 listener. Hence, all actions a macro can legally take must 2634 be defined in this class. Thus we achieve some screening for 2635 security and also thread safety handling. 2636 """ 2637 #-----------------------------------------------------------------
2638 - def __init__(self, personality = None):
2639 if personality is None: 2640 raise gmExceptions.ConstructorError('must specify personality') 2641 self.__personality = personality 2642 self.__attached = 0 2643 self._get_source_personality = None 2644 self.__user_done = False 2645 self.__user_answer = 'no answer yet' 2646 self.__pat = gmPerson.gmCurrentPatient() 2647 2648 self.__auth_cookie = str(random.random()) 2649 self.__pat_lock_cookie = str(random.random()) 2650 self.__lock_after_load_cookie = str(random.random()) 2651 2652 _log.info('slave mode personality is [%s]', personality)
2653 #----------------------------------------------------------------- 2654 # public API 2655 #-----------------------------------------------------------------
2656 - def attach(self, personality = None):
2657 if self.__attached: 2658 _log.error('attach with [%s] rejected, already serving a client', personality) 2659 return (0, _('attach rejected, already serving a client')) 2660 if personality != self.__personality: 2661 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality)) 2662 return (0, _('attach to personality [%s] rejected') % personality) 2663 self.__attached = 1 2664 self.__auth_cookie = str(random.random()) 2665 return (1, self.__auth_cookie)
2666 #-----------------------------------------------------------------
2667 - def detach(self, auth_cookie=None):
2668 if not self.__attached: 2669 return 1 2670 if auth_cookie != self.__auth_cookie: 2671 _log.error('rejecting detach() with cookie [%s]' % auth_cookie) 2672 return 0 2673 self.__attached = 0 2674 return 1
2675 #-----------------------------------------------------------------
2676 - def force_detach(self):
2677 if not self.__attached: 2678 return 1 2679 self.__user_done = False 2680 # FIXME: use self.__sync_cookie for syncing with user interaction 2681 wx.CallAfter(self._force_detach) 2682 return 1
2683 #-----------------------------------------------------------------
2684 - def version(self):
2685 ver = _cfg.get(option = 'client_version') 2686 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
2687 #-----------------------------------------------------------------
2688 - def shutdown_gnumed(self, auth_cookie=None, forced=False):
2689 """Shuts down this client instance.""" 2690 if not self.__attached: 2691 return 0 2692 if auth_cookie != self.__auth_cookie: 2693 _log.error('non-authenticated shutdown_gnumed()') 2694 return 0 2695 wx.CallAfter(self._shutdown_gnumed, forced) 2696 return 1
2697 #-----------------------------------------------------------------
2698 - def raise_gnumed(self, auth_cookie = None):
2699 """Raise ourselves to the top of the desktop.""" 2700 if not self.__attached: 2701 return 0 2702 if auth_cookie != self.__auth_cookie: 2703 _log.error('non-authenticated raise_gnumed()') 2704 return 0 2705 return "cMacroPrimitives.raise_gnumed() not implemented"
2706 #-----------------------------------------------------------------
2707 - def get_loaded_plugins(self, auth_cookie = None):
2708 if not self.__attached: 2709 return 0 2710 if auth_cookie != self.__auth_cookie: 2711 _log.error('non-authenticated get_loaded_plugins()') 2712 return 0 2713 gb = gmGuiBroker.GuiBroker() 2714 return gb['horstspace.notebook.gui'].keys()
2715 #-----------------------------------------------------------------
2716 - def raise_notebook_plugin(self, auth_cookie = None, a_plugin = None):
2717 """Raise a notebook plugin within GNUmed.""" 2718 if not self.__attached: 2719 return 0 2720 if auth_cookie != self.__auth_cookie: 2721 _log.error('non-authenticated raise_notebook_plugin()') 2722 return 0 2723 # FIXME: use semaphore 2724 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin) 2725 return 1
2726 #-----------------------------------------------------------------
2727 - def load_patient_from_external_source(self, auth_cookie = None):
2728 """Load external patient, perhaps create it. 2729 2730 Callers must use get_user_answer() to get status information. 2731 It is unsafe to proceed without knowing the completion state as 2732 the controlled client may be waiting for user input from a 2733 patient selection list. 2734 """ 2735 if not self.__attached: 2736 return (0, _('request rejected, you are not attach()ed')) 2737 if auth_cookie != self.__auth_cookie: 2738 _log.error('non-authenticated load_patient_from_external_source()') 2739 return (0, _('rejected load_patient_from_external_source(), not authenticated')) 2740 if self.__pat.locked: 2741 _log.error('patient is locked, cannot load from external source') 2742 return (0, _('current patient is locked')) 2743 self.__user_done = False 2744 wx.CallAfter(self._load_patient_from_external_source) 2745 self.__lock_after_load_cookie = str(random.random()) 2746 return (1, self.__lock_after_load_cookie)
2747 #-----------------------------------------------------------------
2748 - def lock_loaded_patient(self, auth_cookie = None, lock_after_load_cookie = None):
2749 if not self.__attached: 2750 return (0, _('request rejected, you are not attach()ed')) 2751 if auth_cookie != self.__auth_cookie: 2752 _log.error('non-authenticated lock_load_patient()') 2753 return (0, _('rejected lock_load_patient(), not authenticated')) 2754 # FIXME: ask user what to do about wrong cookie 2755 if lock_after_load_cookie != self.__lock_after_load_cookie: 2756 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie) 2757 return (0, 'patient lock-after-load request rejected, wrong cookie provided') 2758 self.__pat.locked = True 2759 self.__pat_lock_cookie = str(random.random()) 2760 return (1, self.__pat_lock_cookie)
2761 #-----------------------------------------------------------------
2762 - def lock_into_patient(self, auth_cookie = None, search_params = None):
2763 if not self.__attached: 2764 return (0, _('request rejected, you are not attach()ed')) 2765 if auth_cookie != self.__auth_cookie: 2766 _log.error('non-authenticated lock_into_patient()') 2767 return (0, _('rejected lock_into_patient(), not authenticated')) 2768 if self.__pat.locked: 2769 _log.error('patient is already locked') 2770 return (0, _('already locked into a patient')) 2771 searcher = gmPersonSearch.cPatientSearcher_SQL() 2772 if type(search_params) == dict: 2773 idents = searcher.get_identities(search_dict=search_params) 2774 raise Exception("must use dto, not search_dict") 2775 else: 2776 idents = searcher.get_identities(search_term=search_params) 2777 if idents is None: 2778 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict)) 2779 if len(idents) == 0: 2780 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict)) 2781 # FIXME: let user select patient 2782 if len(idents) > 1: 2783 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict)) 2784 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]): 2785 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict)) 2786 self.__pat.locked = True 2787 self.__pat_lock_cookie = str(random.random()) 2788 return (1, self.__pat_lock_cookie)
2789 #-----------------------------------------------------------------
2790 - def unlock_patient(self, auth_cookie = None, unlock_cookie = None):
2791 if not self.__attached: 2792 return (0, _('request rejected, you are not attach()ed')) 2793 if auth_cookie != self.__auth_cookie: 2794 _log.error('non-authenticated unlock_patient()') 2795 return (0, _('rejected unlock_patient, not authenticated')) 2796 # we ain't locked anyways, so succeed 2797 if not self.__pat.locked: 2798 return (1, '') 2799 # FIXME: ask user what to do about wrong cookie 2800 if unlock_cookie != self.__pat_lock_cookie: 2801 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie) 2802 return (0, 'patient unlock request rejected, wrong cookie provided') 2803 self.__pat.locked = False 2804 return (1, '')
2805 #-----------------------------------------------------------------
2806 - def assume_staff_identity(self, auth_cookie = None, staff_name = "Dr.Jekyll", staff_creds = None):
2807 if not self.__attached: 2808 return 0 2809 if auth_cookie != self.__auth_cookie: 2810 _log.error('non-authenticated select_identity()') 2811 return 0 2812 return "cMacroPrimitives.assume_staff_identity() not implemented"
2813 #-----------------------------------------------------------------
2814 - def get_user_answer(self):
2815 if not self.__user_done: 2816 return (0, 'still waiting') 2817 self.__user_done = False 2818 return (1, self.__user_answer)
2819 #----------------------------------------------------------------- 2820 # internal API 2821 #-----------------------------------------------------------------
2822 - def _force_detach(self):
2823 msg = _( 2824 'Someone tries to forcibly break the existing\n' 2825 'controlling connection. This may or may not\n' 2826 'have legitimate reasons.\n\n' 2827 'Do you want to allow breaking the connection ?' 2828 ) 2829 can_break_conn = gmGuiHelpers.gm_show_question ( 2830 aMessage = msg, 2831 aTitle = _('forced detach attempt') 2832 ) 2833 if can_break_conn: 2834 self.__user_answer = 1 2835 else: 2836 self.__user_answer = 0 2837 self.__user_done = True 2838 if can_break_conn: 2839 self.__pat.locked = False 2840 self.__attached = 0 2841 return 1
2842 #-----------------------------------------------------------------
2843 - def _shutdown_gnumed(self, forced=False):
2844 top_win = wx.GetApp().GetTopWindow() 2845 if forced: 2846 top_win.Destroy() 2847 else: 2848 top_win.Close()
2849 #-----------------------------------------------------------------
2851 patient = gmPatSearchWidgets.get_person_from_external_sources(search_immediately = True, activate_immediately = True) 2852 if patient is not None: 2853 self.__user_answer = 1 2854 else: 2855 self.__user_answer = 0 2856 self.__user_done = True 2857 return 1
2858 #===================================================================== 2859 # main 2860 #===================================================================== 2861 if __name__ == '__main__': 2862 2863 if len(sys.argv) < 2: 2864 sys.exit() 2865 2866 if sys.argv[1] != 'test': 2867 sys.exit() 2868 2869 gmI18N.activate_locale() 2870 gmI18N.install_domain() 2871 2872 #--------------------------------------------------------
2873 - def test_placeholders():
2874 handler = gmPlaceholderHandler() 2875 handler.debug = True 2876 2877 for placeholder in ['a', 'b']: 2878 print(handler[placeholder]) 2879 2880 pat = gmPersonSearch.ask_for_patient() 2881 if pat is None: 2882 return 2883 2884 gmPatSearchWidgets.set_active_patient(patient = pat) 2885 2886 print('DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']) 2887 2888 app = wx.PyWidgetTester(size = (200, 50)) 2889 2890 ph = 'progress_notes::ap' 2891 print('%s: %s' % (ph, handler[ph]))
2892 #--------------------------------------------------------
2893 - def test_new_variant_placeholders():
2894 2895 tests = [ 2896 # should work: 2897 '$<lastname>$', 2898 '$<lastname::::3>$', 2899 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$', 2900 2901 # should fail: 2902 'lastname', 2903 '$<lastname', 2904 '$<lastname::', 2905 '$<lastname::>$', 2906 '$<lastname::abc>$', 2907 '$<lastname::abc::>$', 2908 '$<lastname::abc::3>$', 2909 '$<lastname::abc::xyz>$', 2910 '$<lastname::::>$', 2911 '$<lastname::::xyz>$', 2912 2913 '$<date_of_birth::%Y-%m-%d>$', 2914 '$<date_of_birth::%Y-%m-%d::3>$', 2915 '$<date_of_birth::%Y-%m-%d::>$', 2916 2917 # should work: 2918 '$<adr_location::home::35>$', 2919 '$<gender_mapper::male//female//other::5>$', 2920 '$<current_meds::==> %(product)s %(preparation)s (%(substance)s) <==\n::50>$', 2921 '$<allergy_list::%(descriptor)s, >$', 2922 '$<current_meds_table::latex//>$' 2923 2924 # 'firstname', 2925 # 'title', 2926 # 'date_of_birth', 2927 # 'progress_notes', 2928 # 'soap', 2929 # 'soap_s', 2930 # 'soap_o', 2931 # 'soap_a', 2932 # 'soap_p', 2933 2934 # 'soap', 2935 # 'progress_notes', 2936 # 'date_of_birth' 2937 ] 2938 2939 # tests = [ 2940 # '$<latest_vaccs_table::latex>$' 2941 # ] 2942 2943 pat = gmPersonSearch.ask_for_patient() 2944 if pat is None: 2945 return 2946 2947 gmPatSearchWidgets.set_active_patient(patient = pat) 2948 2949 handler = gmPlaceholderHandler() 2950 handler.debug = True 2951 2952 for placeholder in tests: 2953 print(placeholder, "=>", handler[placeholder]) 2954 print("--------------") 2955 input()
2956 2957 # print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d'] 2958 2959 # app = wx.PyWidgetTester(size = (200, 50)) 2960 2961 # ph = 'progress_notes::ap' 2962 # print '%s: %s' % (ph, handler[ph]) 2963 2964 #--------------------------------------------------------
2965 - def test_scripting():
2966 from Gnumed.pycommon import gmScriptingListener 2967 import xmlrpc.client 2968 2969 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999) 2970 2971 s = xmlrpc.client.ServerProxy('http://localhost:9999') 2972 print("should fail:", s.attach()) 2973 print("should fail:", s.attach('wrong cookie')) 2974 print("should work:", s.version()) 2975 print("should fail:", s.raise_gnumed()) 2976 print("should fail:", s.raise_notebook_plugin('test plugin')) 2977 print("should fail:", s.lock_into_patient('kirk, james')) 2978 print("should fail:", s.unlock_patient()) 2979 status, conn_auth = s.attach('unit test') 2980 print("should work:", status, conn_auth) 2981 print("should work:", s.version()) 2982 print("should work:", s.raise_gnumed(conn_auth)) 2983 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james') 2984 print("should work:", status, pat_auth) 2985 print("should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie')) 2986 print("should work", s.unlock_patient(conn_auth, pat_auth)) 2987 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'} 2988 status, pat_auth = s.lock_into_patient(conn_auth, data) 2989 print("should work:", status, pat_auth) 2990 print("should work", s.unlock_patient(conn_auth, pat_auth)) 2991 print(s.detach('bogus detach cookie')) 2992 print(s.detach(conn_auth)) 2993 del s 2994 2995 listener.shutdown()
2996 #--------------------------------------------------------
2997 - def test_placeholder_regex():
2998 2999 import re as regex 3000 3001 tests = [ 3002 ' $<lastname>$ ', 3003 ' $<lastname::::3>$ ', 3004 3005 # should fail: 3006 '$<date_of_birth::%Y-%m-%d>$', 3007 '$<date_of_birth::%Y-%m-%d::3>$', 3008 '$<date_of_birth::%Y-%m-%d::>$', 3009 3010 '$<adr_location::home::35>$', 3011 '$<gender_mapper::male//female//other::5>$', 3012 '$<current_meds::==> %(product)s %(preparation)s (%(substance)s) <==\\n::50>$', 3013 '$<allergy_list::%(descriptor)s, >$', 3014 3015 '\\noindent Patient: $<lastname>$, $<firstname>$', 3016 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$', 3017 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(product)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$' 3018 ] 3019 3020 tests = [ 3021 3022 'junk $<lastname::::3>$ junk', 3023 'junk $<lastname::abc::3>$ junk', 3024 'junk $<lastname::abc>$ junk', 3025 'junk $<lastname>$ junk', 3026 3027 'junk $<lastname>$ junk $<firstname>$ junk', 3028 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk', 3029 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk', 3030 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk' 3031 3032 ] 3033 3034 tests = [ 3035 # u'junk $<<<date_of_birth::%Y %B %d $<inner placeholder::%Y %B %d::20>$::20>>>$ junk', 3036 # u'junk $<date_of_birth::%Y %B %d::20>$ $<date_of_birth::%Y %B %d::20>$', 3037 # u'junk $<date_of_birth::%Y %B %d::>$ $<date_of_birth::%Y %B %d::20>$ $<<date_of_birth::%Y %B %d::20>>$', 3038 # u'junk $<date_of_birth::::20>$', 3039 # u'junk $<date_of_birth::::>$', 3040 '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', 3041 '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', 3042 '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', 3043 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::->>>$ 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::3->>>$ junk', 3046 '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', 3047 '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', 3048 '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', 3049 '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', 3050 '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', 3051 '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', 3052 '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', 3053 ] 3054 3055 tests = [ 3056 'junk $<<<should pass::template::>>>$ junk', 3057 'junk $<<<should pass::template::10>>>$ junk', 3058 'junk $<<<should pass::template::10-20>>>$ junk', 3059 'junk $<<<should pass::template $<<dummy::template 2::10>>$::>>>$ junk', 3060 'junk $<<<should pass::template $<dummy::template 2::10>$::>>>$ junk', 3061 3062 'junk $<<<should pass::template::>>>$ junk $<<<should pass 2::template 2::>>>$ junk', 3063 'junk $<<<should pass::template::>>>$ junk $<<should pass 2::template 2::>>$ junk', 3064 'junk $<<<should pass::template::>>>$ junk $<should pass 2::template 2::>$ junk', 3065 3066 'junk $<<<should fail::template $<<<dummy::template 2::10>>>$::>>>$ junk', 3067 3068 'junk $<<<should fail::template::10->>>$ junk', 3069 'junk $<<<should fail::template::10->>>$ junk', 3070 'junk $<<<should fail::template::10->>>$ junk', 3071 'junk $<<<should fail::template::10->>>$ junk', 3072 'junk $<first_pass::junk $<<<3rd_pass::template::20>>>$ junk::8-10>$ junk' 3073 ] 3074 3075 #print "testing placeholder regex:", first_pass_placeholder_regex 3076 ##print "testing placeholder regex:", second_pass_placeholder_regex 3077 ##print "testing placeholder regex:", third_pass_placeholder_regex 3078 #print "" 3079 #for t in tests: 3080 # print 'line: "%s"' % t 3081 # phs = regex.findall(first_pass_placeholder_regex, t, regex.IGNORECASE) 3082 # #phs = regex.findall(second_pass_placeholder_regex, t, regex.IGNORECASE) 3083 # #phs = regex.findall(third_pass_placeholder_regex, t, regex.IGNORECASE) 3084 # print " %s placeholders:" % len(phs) 3085 # for p in phs: 3086 # print ' => ', p 3087 # print " " 3088 3089 all_tests = { 3090 first_pass_placeholder_regex: [ 3091 # different lengths/regions 3092 ('junk $<first_level::template::>$ junk', ['$<first_level::template::>$']), 3093 ('junk $<first_level::template::10>$ junk', ['$<first_level::template::10>$']), 3094 ('junk $<first_level::template::10-12>$ junk', ['$<first_level::template::10-12>$']), 3095 3096 # inside is other-level: 3097 ('junk $<first_level::$<<insert::insert_template::0>>$::10-12>$ junk', ['$<first_level::$<<insert::insert_template::0>>$::10-12>$']), 3098 ('junk $<first_level::$<<<insert::insert_template::0>>>$::10-12>$ junk', ['$<first_level::$<<<insert::insert_template::0>>>$::10-12>$']), 3099 3100 # outside is other-level: 3101 ('junk $<<second_level::$<insert::insert_template::0>$::10-12>>$ junk', ['$<insert::insert_template::0>$']), 3102 ('junk $<<<third_level::$<insert::insert_template::0>$::10-12>>>$ junk', ['$<insert::insert_template::0>$']), 3103 3104 # other level on same line 3105 ('junk $<first_level 1::template 1::>$ junk $<<second_level 2::template 2::>>$ junk', ['$<first_level 1::template 1::>$']), 3106 ('junk $<first_level 1::template 1::>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<first_level 1::template 1::>$']), 3107 3108 # this should produce 2 matches 3109 ('junk $<first_level 1::template 1::>$ junk $<first_level 2::template 2::>$ junk', ['$<first_level 1::template 1::>$', '$<first_level 2::template 2::>$']), 3110 3111 # this will produce a mismatch, due to illegal nesting of same-level placeholders 3112 ('returns illegal match: junk $<first_level::$<insert::insert_template::0>$::10-12>$ junk', ['$<first_level::$<insert::insert_template::0>$::10-12>$']), 3113 ], 3114 second_pass_placeholder_regex: [ 3115 # different lengths/regions 3116 ('junk $<<second_level::template::>>$ junk', ['$<<second_level::template::>>$']), 3117 ('junk $<<second_level::template::10>>$ junk', ['$<<second_level::template::10>>$']), 3118 ('junk $<<second_level::template::10-12>>$ junk', ['$<<second_level::template::10-12>>$']), 3119 3120 # inside is other-level: 3121 ('junk $<<second_level::$<insert::insert_template::0>$::10-12>>$ junk', ['$<<second_level::$<insert::insert_template::0>$::10-12>>$']), 3122 ('junk $<<second_level::$<<<insert::insert_template::0>>>$::10-12>>$ junk', ['$<<second_level::$<<<insert::insert_template::0>>>$::10-12>>$']), 3123 3124 # outside is other-level: 3125 ('junk $<first_level::$<<insert::insert_template::0>>$::10-12>$ junk', ['$<<insert::insert_template::0>>$']), 3126 ('junk $<<<third_level::$<<insert::insert_template::0>>$::10-12>>>$ junk', ['$<<insert::insert_template::0>>$']), 3127 3128 # other level on same line 3129 ('junk $<first_level 1::template 1::>$ junk $<<second_level 2::template 2::>>$ junk', ['$<<second_level 2::template 2::>>$']), 3130 ('junk $<<second_level 1::template 1::>>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<<second_level 1::template 1::>>$']), 3131 3132 # this should produce 2 matches 3133 ('junk $<<second_level 1::template 1::>>$ junk $<<second_level 2::template 2::>>$ junk', ['$<<second_level 1::template 1::>>$', '$<<second_level 2::template 2::>>$']), 3134 3135 # this will produce a mismatch, due to illegal nesting of same-level placeholders 3136 ('returns illegal match: junk $<<second_level::$<<insert::insert_template::0>>$::10-12>>$ junk', ['$<<second_level::$<<insert::insert_template::0>>$::10-12>>$']), 3137 3138 ], 3139 third_pass_placeholder_regex: [ 3140 # different lengths/regions 3141 ('junk $<<<third_level::template::>>>$ junk', ['$<<<third_level::template::>>>$']), 3142 ('junk $<<<third_level::template::10>>>$ junk', ['$<<<third_level::template::10>>>$']), 3143 ('junk $<<<third_level::template::10-12>>>$ junk', ['$<<<third_level::template::10-12>>>$']), 3144 3145 # inside is other-level: 3146 ('junk $<<<third_level::$<<insert::insert_template::0>>$::10-12>>>$ junk', ['$<<<third_level::$<<insert::insert_template::0>>$::10-12>>>$']), 3147 ('junk $<<<third_level::$<insert::insert_template::0>$::10-12>>>$ junk', ['$<<<third_level::$<insert::insert_template::0>$::10-12>>>$']), 3148 3149 # outside is other-level: 3150 ('junk $<<second_level::$<<<insert::insert_template::0>>>$::10-12>>$ junk', ['$<<<insert::insert_template::0>>>$']), 3151 ('junk $<first_level::$<<<insert::insert_template::0>>>$::10-12>$ junk', ['$<<<insert::insert_template::0>>>$']), 3152 3153 # other level on same line 3154 ('junk $<first_level 1::template 1::>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<<<third_level 2::template 2::>>>$']), 3155 ('junk $<<second_level 1::template 1::>>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<<<third_level 2::template 2::>>>$']), 3156 3157 # this will produce a mismatch, due to illegal nesting of same-level placeholders 3158 ('returns illegal match: junk $<<<third_level::$<<<insert::insert_template::0>>>$::10-12>>>$ junk', ['$<<<third_level::$<<<insert::insert_template::0>>>$::10-12>>>$']), 3159 ] 3160 } 3161 3162 for pattern in [first_pass_placeholder_regex, second_pass_placeholder_regex, third_pass_placeholder_regex]: 3163 print("") 3164 print("-----------------------------") 3165 print("regex:", pattern) 3166 tests = all_tests[pattern] 3167 for t in tests: 3168 line, expected_results = t 3169 phs = regex.findall(pattern, line, regex.IGNORECASE) 3170 if len(phs) > 0: 3171 if phs == expected_results: 3172 continue 3173 3174 print("") 3175 print("failed") 3176 print("line:", line) 3177 3178 if len(phs) == 0: 3179 print("no match") 3180 continue 3181 3182 if len(phs) > 1: 3183 print("several matches") 3184 for r in expected_results: 3185 print("expected:", r) 3186 for p in phs: 3187 print("found:", p) 3188 continue 3189 3190 print("unexpected match") 3191 print("expected:", expected_results) 3192 print("found: ", phs)
3193 3194 #--------------------------------------------------------
3195 - def test_placeholder():
3196 3197 phs = [ 3198 #u'emr_journal::soapu //%(clin_when)s %(modified_by)s %(soap_cat)s %(narrative)s//1000 days::', 3199 #u'free_text::placeholder test::9999', 3200 #u'soap_for_encounters:://::9999', 3201 #u'soap_p', 3202 #u'encounter_list::%(started)s: %(assessment_of_encounter)s::30', 3203 #u'patient_comm::homephone::1234', 3204 #u'$<patient_address::work::1234>$', 3205 #u'adr_region::home::1234', 3206 #u'adr_country::fehlt::1234', 3207 #u'adr_subunit::fehlt::1234', 3208 #u'adr_suburb::fehlt-auch::1234', 3209 #u'external_id::Starfleet Serial Number//Star Fleet Central Staff Office::1234', 3210 #u'primary_praxis_provider', 3211 #u'current_provider::::3-5', 3212 #u'current_provider_external_id::Starfleet Serial Number//Star Fleet Central Staff Office::1234', 3213 #u'current_provider_external_id::LANR//LÄK::1234' 3214 #u'$<current_provider_external_id::KV-LANR//KV::1234>$' 3215 #u'primary_praxis_provider_external_id::LANR//LÄK::1234' 3216 #u'form_name_long::::1234', 3217 #u'form_name_long::::5', 3218 #u'form_name_long::::', 3219 #u'form_version::::5', 3220 #u'$<current_meds::\item %(product)s %(preparation)s (%(substance)s) from %(started)s for %(duration)s as %(schedule)s until %(discontinued)s\\n::250>$', 3221 #u'$<vaccination_history::%(date_given)s: %(vaccine)s [%(batch_no)s] %(l10n_indications)s::250>$', 3222 #u'$<date_of_birth::%Y %B %d::20>$', 3223 #u'$<date_of_birth::%Y %B %d::>$', 3224 #u'$<date_of_birth::::20>$', 3225 #u'$<date_of_birth::::>$', 3226 #u'$<patient_tags::Tag "%(l10n_description)s": %(comment)s//\\n- ::250>$', 3227 #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>$', 3228 #u'$<patient_photo::\includegraphics[width=60mm]{%s}//image/png//.png::250>$', 3229 #u'$<data_snippet::binary_test_snippet//path=<%s>//image/png//.png::250>$', 3230 #u'$<data_snippet::autograph-LMcC//path=<%s>//image/jpg//.jpg::250>$', 3231 #u'$<current_meds::%s ($<lastname::::50>$)//select::>$', 3232 #u'$<current_meds::%s//select::>$', 3233 #u'$<soap_by_issue::soapu //%Y %b %d//%(narrative)s::1000>$', 3234 #u'$<soap_by_episode::soapu //%Y %b %d//%(narrative)s::1000>$', 3235 #u'$<documents::select//description//document %(clin_when)s: %(l10n_type)s// file: %(fullpath)s (<some path>/%(name)s)//~/gnumed/export/::>$', 3236 #u'$<soap::soapu //%s::9999>$', 3237 #u'$<soap::soapu //%(soap_cat)s: %(date)s | %(provider)s | %(narrative)s::9999>$' 3238 #u'$<test_results:://%c::>$' 3239 #u'$<test_results::%(unified_abbrev)s: %(unified_val)s %(val_unit)s//%c::>$' 3240 #u'$<reminders:://::>$' 3241 #u'$<current_meds_for_rx::%(product)s (%(contains)s): dispense %(amount2dispense)s ::>$' 3242 #u'$<praxis::%(branch)s (%(praxis)s)::>$' 3243 #u'$<praxis_address::::120>$' 3244 #u'$<praxis_id::::120>$' 3245 #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>$' 3246 3247 #u'$<receiver_name::%s::120>$', 3248 #u'$<receiver_street::%s//a::120>$', 3249 #u'$<receiver_number:: %s//a::120>$', 3250 #u'$<receiver_subunit:: %s::120>$', 3251 #u'$<receiver_postcode::%s//b::120>$', 3252 #u'$<receiver_location:: %s::120>$', 3253 #u'$<receiver_country::, %s::120>$', 3254 #u'$<external_care::%(issue)s: %(provider)s of %(unit)s@%(organization)s (%(comment)s)::1024>$', 3255 #u'$<url_escape::hello world ü::>$', 3256 #u'$<substance_abuse::%(substance)s (%(harmful_use_type)s) last=%(last_checked_when)s stop=%(discontinued)s // %(notes)s::>$', 3257 #u'bill_adr_region::region %s::1234', 3258 #u'bill_adr_country::%s::1234', 3259 #u'bill_adr_subunit::subunit: %s::1234', 3260 #u'bill_adr_suburb::-> %s::1234', 3261 #u'bill_adr_street::::1234', 3262 #u'bill_adr_number::%s::1234', 3263 '$<diagnoses::\listitem %s::>$' 3264 ] 3265 3266 handler = gmPlaceholderHandler() 3267 handler.debug = True 3268 3269 gmStaff.set_current_provider_to_logged_on_user() 3270 gmPraxisWidgets.set_active_praxis_branch(no_parent = True) 3271 pat = gmPersonSearch.ask_for_patient() 3272 if pat is None: 3273 return 3274 gmPatSearchWidgets.set_active_patient(patient = pat) 3275 3276 app = wx.PyWidgetTester(size = (200, 50)) 3277 #handler.set_placeholder('form_name_long', 'ein Testformular') 3278 for ph in phs: 3279 print(ph) 3280 print(" result:") 3281 print(' %s' % handler[ph])
3282 #handler.unset_placeholder('form_name_long') 3283 3284 #--------------------------------------------------------
3285 - def test():
3286 pat = gmPersonSearch.ask_for_patient() 3287 if pat is None: 3288 sys.exit() 3289 gmPerson.set_active_patient(patient = pat) 3290 from Gnumed.wxpython import gmMedicationWidgets 3291 gmMedicationWidgets.manage_substance_intakes()
3292 3293 #--------------------------------------------------------
3294 - def test_show_phs():
3295 show_placeholders()
3296 3297 #-------------------------------------------------------- 3298 3299 app = wx.App() 3300 3301 #test_placeholders() 3302 #test_new_variant_placeholders() 3303 #test_scripting() 3304 #test_placeholder_regex() 3305 #test() 3306 test_placeholder() 3307 #test_show_phs() 3308