Package Gnumed :: Package exporters :: Module gmPatientExporter |
[frames] | no frames]

# Source Code for Module Gnumed.exporters.gmPatientExporter

       1  """GNUmed simple ASCII EMR export tool.
       2
       3  TODO:
       4  - GUI mode:
       5    - post-0.1 !
       6    - allow user to select patient
       7    - allow user to pick episodes/encounters/etc from list
       8  - output modes:
       9    - HTML - post-0.1 !
      10  """
      11  #============================================================
      12  __author__ = "Carlos Moro"
      13  __license__ = 'GPL'
      14
      15  import os.path, sys, types, time, codecs, datetime as pyDT, logging, shutil
      16
      17
      18  import mx.DateTime.Parser as mxParser
      19  import mx.DateTime as mxDT
      20
      21
      22  if __name__ == '__main__':
      23          sys.path.insert(0, '../../')
      24
      25  from Gnumed.pycommon import gmI18N
      26
      27  if __name__ == '__main__':
      28          gmI18N.activate_locale()
      29          gmI18N.install_domain()
      30
      31  from Gnumed.pycommon import gmExceptions, gmNull, gmPG2, gmTools, gmDateTime
      32  from Gnumed.business import gmClinicalRecord, gmPerson, gmAllergy, gmDemographicRecord, gmClinNarrative, gmPersonSearch
      33
      34
      35  _log = logging.getLogger('gm.export')
      36  #============================================================
      37 -class cEmrExport:
      38
      39      #--------------------------------------------------------
      40 -    def __init__(self, constraints = None, fileout = None, patient = None):
      41          """
      42          Constructs a new instance of exporter
      43
      44          constraints - Exporter constraints for filtering clinical items
      45          fileout - File-like object as target for dumping operations
      46          """
      47          if constraints is None:
      48              # default constraints to None for complete emr dump
      49              self.__constraints = {
      50                  'since': None,
      51                  'until': None,
      52                  'encounters': None,
      53                  'episodes': None,
      54                  'issues': None
      55              }
      56          else:
      57              self.__constraints = constraints
      58          self.__target = fileout
      59          self.__patient = patient
      60          self.lab_new_encounter = True
      61          self.__filtered_items = []
      62      #--------------------------------------------------------
      63 -    def set_constraints(self, constraints = None):
      64          """Sets exporter constraints.
      65
      66          constraints - Exporter constraints for filtering clinical items
      67          """
      68          if constraints is None:
      69              # default constraints to None for complete emr dump
      70              self.__constraints = {
      71                  'since': None,
      72                  'until': None,
      73                  'encounters': None,
      74                  'episodes': None,
      75                  'issues': None
      76              }
      77          else:
      78              self.__constraints = constraints
      79          return True
      80      #--------------------------------------------------------
      81 -    def get_constraints(self):
      82          """
      83          Retrieve exporter constraints
      84          """
      85          return self.__constraints
      86      #--------------------------------------------------------
      87 -    def set_patient(self, patient=None):
      88          """
      89              Sets exporter patient
      90
      91              patient - Patient whose data are to be dumped
      92          """
      93          if patient is None:
      94              _log.error("can't set None patient for exporter")
      95              return
      96          self.__patient = patient
      97      #--------------------------------------------------------
      98 -    def set_output_file(self, target=None):
      99          """
     100              Sets exporter output file
     101
     102              @param file_name - The file to dump the EMR to
     103              @type file_name - FileType
     104          """
     105          self.__target = target
     106      #--------------------------------------------------------
     107 -    def get_patient(self):
     108          """
     109              Retrieves patient whose data are to be dumped
     110          """
     111          return self.__patient
     112      #--------------------------------------------------------
     113 -    def cleanup(self):
     114          """
     115              Exporter class cleanup code
     116          """
     117          pass
     118      #--------------------------------------------------------
     119 -    def __dump_vacc_table(self, vacc_regimes):
     120          """
     121          Retrieves string containg ASCII vaccination table
     122          """
     123          emr = self.__patient.get_emr()
     124          # patient dob
     125          patient_dob = self.__patient['dob']
     126          date_length = len(gmDateTime.pydt_strftime(patient_dob, '%Y %b %d')) + 2
     127
     128          # dictionary of pairs indication : scheduled vaccination
     129          vaccinations4regimes = {}
     130          for a_vacc_regime in vacc_regimes:
     131              indication = a_vacc_regime['indication']
     132              vaccinations4regimes[indication] = emr.get_scheduled_vaccinations(indications=[indication])
     133          # vaccination regimes count
     134          chart_columns = len(vacc_regimes)
     135          # foot headers
     136          foot_headers = ['last booster', 'next booster']
     137          # string for both: ending of vaccinations; non boosters needed
     138          ending_str = '='
     139
     140          # chart row count, columns width and vaccination dictionary of pairs indication : given shot
     141          column_widths = []
     142          chart_rows = -1
     143          vaccinations = {}
     144          temp = -1
     145          for foot_header in foot_headers: # first column width
     146              if len(foot_header) > temp:
     147                  temp = len(foot_header)
     148          column_widths.append(temp)
     149          for a_vacc_regime in vacc_regimes:
     150              if a_vacc_regime['shots'] > chart_rows: # max_seq  -> row count
     151                  chart_rows = a_vacc_regime['shots']
     152              if (len(a_vacc_regime['l10n_indication'])) > date_length: # l10n indication -> column width
     153                  column_widths.append(len(a_vacc_regime['l10n_indication']))
     154              else:
     155                  column_widths.append(date_length)  # date -> column width at least
     156              vaccinations[a_vacc_regime['indication']] = emr.get_vaccinations(indications=[a_vacc_regime['indication']]) # given shots 4 indication
     157
     158          # patient dob in top of vaccination chart
     159          txt = '\nDOB: %s' % (gmDateTime.pydt_strftime(patient_dob, '%Y %b %d')) + '\n'
     160
     161          # vacc chart table headers
     162          # top ---- header line
     163          for column_width in column_widths:
     164              txt += column_width * '-' + '-'
     165          txt += '\n'
     166          # indication names header line
     167          txt += column_widths[0] * ' ' + '|'
     168          col_index = 1
     169          for a_vacc_regime in vacc_regimes:
     170              txt +=    a_vacc_regime['l10n_indication'] + (column_widths[col_index] - len(a_vacc_regime['l10n_indication'])) * ' ' + '|'
     171              col_index += 1
     172          txt +=    '\n'
     173          # bottom ---- header line
     174          for column_width in column_widths:
     175              txt += column_width * '-' + '-'
     176          txt += '\n'
     177
     178          # vacc chart data
     179          due_date = None
     180          # previously displayed date list
     181          prev_displayed_date = [patient_dob]
     182          for a_regime in vacc_regimes:
     183              prev_displayed_date.append(patient_dob) # initialice with patient dob (useful for due first shot date calculation)
     184          # iterate data rows
     185          for row_index in range(0, chart_rows):
     186              row_header = '#%s' %(row_index+1)
     187              txt += row_header + (column_widths[0] - len(row_header)) * ' ' + '|'
     188
     189              for col_index in range(1, chart_columns+1):
     190                  indication =vacc_regimes[col_index-1]['indication']
     191                  seq_no = vacc_regimes[col_index-1]['shots']
     192                  if row_index == seq_no: # had just ended scheduled vaccinations
     193                       txt += ending_str * column_widths[col_index] + '|'
     194                  elif row_index < seq_no: # vaccination scheduled
     195                      try:
     196                          vacc_date = vaccinations[indication][row_index]['date'] # vaccination given
     197                          vacc_date_str = gmDateTime.pydt_strftime(vacc_date, '%Y %b %d')
     198                          txt +=    vacc_date_str + (column_widths[col_index] - len(vacc_date_str)) * ' ' + '|'
     199                          prev_displayed_date[col_index] = vacc_date
     200                      except:
     201                          if row_index == 0: # due first shot
     202                              due_date = prev_displayed_date[col_index] + vaccinations4regimes[indication][row_index]['age_due_min'] # FIXME 'age_due_min' not properly retrieved
     203                          else: # due any other than first shot
     204                              due_date = prev_displayed_date[col_index] + vaccinations4regimes[indication][row_index]['min_interval']
     205                          txt += '('+ due_date.strftime('%Y-%m-%d') + ')' + (column_widths[col_index] - date_length) * ' ' + '|'
     206                          prev_displayed_date[col_index] = due_date
     207                  else: # not scheduled vaccination at that position
     208                      txt += column_widths[col_index] * ' ' + '|'
     209              txt += '\n' # end of scheduled vaccination dates display
     210              for column_width in column_widths: # ------ separator line
     211                  txt += column_width * '-' + '-'
     212              txt += '\n'
     213
     214          # scheduled vaccination boosters (date retrieving)
     215          all_vreg_boosters = []
     216          for a_vacc_regime in vacc_regimes:
     217              vaccs4indication = vaccinations[a_vacc_regime['indication']] # iterate over vaccinations by indication
     218              given_boosters = [] # will contain given boosters for current indication
     219              for a_vacc in vaccs4indication:
     220                  try:
     221                       if a_vacc['is_booster']:
     222                           given_boosters.append(a_vacc)
     223                  except:
     224                      # not a booster
     225                      pass
     226              if len(given_boosters) > 0:
     227                  all_vreg_boosters.append(given_boosters[len(given_boosters)-1]) # last of given boosters
     228              else:
     229                  all_vreg_boosters.append(None)
     230
     231          # next booster in schedule
     232          all_next_boosters = []
     233          for a_booster in all_vreg_boosters:
     234              all_next_boosters.append(None)
     235          # scheduled vaccination boosters (displaying string)
     236          cont = 0
     237          for a_vacc_regime in vacc_regimes:
     238              vaccs = vaccinations4regimes[a_vacc_regime['indication']]
     239              if vaccs[len(vaccs)-1]['is_booster'] == False: # booster is not scheduled/needed
     240                  all_vreg_boosters[cont] = ending_str * column_widths[cont+1]
     241                  all_next_boosters[cont] = ending_str * column_widths[cont+1]
     242              else:
     243                  indication = vacc_regimes[cont]['indication']
     244                  if len(vaccinations[indication]) > vacc_regimes[cont]['shots']: # boosters given
     245                      all_vreg_boosters[cont] = vaccinations[indication][len(vaccinations[indication])-1]['date'].strftime('%Y-%m-%d') # show last given booster date
     246                      scheduled_booster = vaccinations4regimes[indication][len(vaccinations4regimes[indication])-1]
     247                      booster_date = vaccinations[indication][len(vaccinations[indication])-1]['date'] + scheduled_booster['min_interval']
     248                      if booster_date < mxDT.today():
     249                          all_next_boosters[cont] = '<(' + booster_date.strftime('%Y-%m-%d') + ')>' # next booster is due
     250                      else:
     251                          all_next_boosters[cont] = booster_date.strftime('%Y-%m-%d')
     252                  elif len(vaccinations[indication]) == vacc_regimes[cont]['shots']: # just finished vaccinations, begin boosters
     253                      all_vreg_boosters[cont] = column_widths[cont+1] * ' '
     254                      scheduled_booster = vaccinations4regimes[indication][len(vaccinations4regimes[indication])-1]
     255                      booster_date = vaccinations[indication][len(vaccinations[indication])-1]['date'] + scheduled_booster['min_interval']
     256                      if booster_date < mxDT.today():
     257                          all_next_boosters[cont] = '<(' + booster_date.strftime('%Y-%m-%d') + ')>' # next booster is due
     258                      else:
     259                          all_next_boosters[cont] = booster_date.strftime('%Y-%m-%d')
     260                  else:
     261                      all_vreg_boosters[cont] = column_widths[cont+1] * ' '  # unfinished schedule
     262                      all_next_boosters[cont] = column_widths[cont+1] * ' '
     263              cont += 1
     264
     265          # given boosters
     266          foot_header = foot_headers[0]
     267          col_index = 0
     268          txt += foot_header + (column_widths[0] - len(foot_header)) * ' ' + '|'
     269          col_index += 1
     270          for a_vacc_regime in vacc_regimes:
     271              txt +=    str(all_vreg_boosters[col_index-1]) + (column_widths[col_index] - len(str(all_vreg_boosters[col_index-1]))) * ' ' + '|'
     272              col_index += 1
     273          txt +=    '\n'
     274          for column_width in column_widths:
     275              txt += column_width * '-' + '-'
     276          txt += '\n'
     277
     278          # next booster
     279          foot_header = foot_headers[1]
     280          col_index = 0
     281          txt += foot_header + (column_widths[0] - len(foot_header)) * ' ' + '|'
     282          col_index += 1
     283          for a_vacc_regime in vacc_regimes:
     284              txt +=    str(all_next_boosters[col_index-1]) + (column_widths[col_index] - len(str(all_next_boosters[col_index-1]))) * ' ' + '|'
     285              col_index += 1
     286          txt +=    '\n'
     287          for column_width in column_widths:
     288              txt += column_width * '-' + '-'
     289          txt += '\n'
     290
     291          self.__target.write(txt)
     292      #--------------------------------------------------------
     293 -    def get_vacc_table(self):
     294          """
     295          Iterate over patient scheduled regimes preparing vacc tables dump
     296          """
     297
     298          emr = self.__patient.get_emr()
     299
     300          # vaccination regimes
     301          all_vacc_regimes = emr.get_scheduled_vaccination_regimes()
     302          # Configurable: vacc regimes per displayed table
     303          # FIXME: option, post 0.1 ?
     304          max_regs_per_table = 4
     305
     306          # Iterate over patient scheduled regimes dumping in tables of
     307          # max_regs_per_table regimes per table
     308          reg_count = 0
     309          vacc_regimes = []
     310          for total_reg_count in range(0,len(all_vacc_regimes)):
     311              if reg_count%max_regs_per_table == 0:
     312                  if len(vacc_regimes) > 0:
     313                      self.__dump_vacc_table(vacc_regimes)
     314                  vacc_regimes = []
     315                  reg_count = 0
     316              vacc_regimes.append(all_vacc_regimes[total_reg_count])
     317              reg_count += 1
     318          if len(vacc_regimes) > 0:
     319              self.__dump_vacc_table(vacc_regimes)
     320
     321      #--------------------------------------------------------
     322 -    def dump_item_fields(self, offset, item, field_list):
     323          """
     324              Dump information related to the fields of a clinical item
     325              offset - Number of left blank spaces
     326              item - Item of the field to dump
     327              fields - Fields to dump
     328          """
     329          txt = ''
     330          for a_field in field_list:
     331              if type(a_field) is not types.UnicodeType:
     332                  a_field = unicode(a_field, encoding='latin1', errors='replace')
     333              txt += u'%s%s%s' % ((offset * u' '), a_field, gmTools.coalesce(item[a_field], u'\n', template_initial = u': %s\n'))
     334          return txt
     335      #--------------------------------------------------------
     336 -    def get_allergy_output(self, allergy, left_margin = 0):
     337          """
     338              Dumps allergy item data
     339              allergy - Allergy item to dump
     340              left_margin - Number of spaces on the left margin
     341          """
     342          txt = ''
     343          txt += left_margin*' ' + _('Allergy')  + ': \n'
     344          txt += self.dump_item_fields((left_margin+3), allergy, ['allergene', 'substance', 'generic_specific','l10n_type', 'definite', 'reaction'])
     345          return txt
     346      #--------------------------------------------------------
     347 -    def get_vaccination_output(self, vaccination, left_margin = 0):
     348          """
     349              Dumps vaccination item data
     350              vaccination - Vaccination item to dump
     351              left_margin - Number of spaces on the left margin
     352          """
     353          txt = ''
     354          txt += left_margin*' ' + _('Vaccination') + ': \n'
     355          txt += self.dump_item_fields((left_margin+3), vaccination, ['l10n_indication', 'vaccine', 'batch_no', 'site', 'narrative'])
     356          return txt
     357      #--------------------------------------------------------
     358 -    def get_lab_result_output(self, lab_result, left_margin = 0):
     359          """
     360              Dumps lab result item data
     361              lab_request - Lab request item to dump
     362              left_margin - Number of spaces on the left margin
     363          """
     364          txt = ''
     365          if self.lab_new_encounter:
     366              txt += (left_margin)*' ' + _('Lab result') + ': \n'
     367          txt += (left_margin+3) * ' ' + lab_result['unified_name']  + ': ' + lab_result['unified_val']+ ' ' + lab_result['val_unit'] + ' (' + lab_result['material'] + ')' + '\n'
     368          return txt
     369      #--------------------------------------------------------
     370 -    def get_item_output(self, item, left_margin = 0):
     371          """
     372              Obtains formatted clinical item output dump
     373              item - The clinical item to dump
     374              left_margin - Number of spaces on the left margin
     375          """
     376          txt = ''
     377          if isinstance(item, gmAllergy.cAllergy):
     378              txt += self.get_allergy_output(item, left_margin)
     379  #        elif isinstance(item, gmVaccination.cVaccination):
     380   #           txt += self.get_vaccination_output(item, left_margin)
     381  #        elif isinstance(item, gmPathLab.cLabResult):
     382   #           txt += self.get_lab_result_output(item, left_margin)
     383    #          self.lab_new_encounter = False
     384          return txt
     385      #--------------------------------------------------------
     386 -    def __fetch_filtered_items(self):
     387          """
     388              Retrieve patient clinical items filtered by multiple constraints
     389          """
     390          if not self.__patient.connected:
     391              return False
     392          emr = self.__patient.get_emr()
     393          filtered_items = []
     394          filtered_items.extend(emr.get_allergies(
     395              since=self.__constraints['since'],
     396              until=self.__constraints['until'],
     397              encounters=self.__constraints['encounters'],
     398              episodes=self.__constraints['episodes'],
     399              issues=self.__constraints['issues'])
     400          )
     401          self.__filtered_items = filtered_items
     402          return True
     403      #--------------------------------------------------------
     404 -    def get_allergy_summary(self, allergy, left_margin = 0):
     405          """
     406              Dumps allergy item data summary
     407              allergy - Allergy item to dump
     408              left_margin - Number of spaces on the left margin
     409          """
     410          txt = _('%sAllergy: %s, %s (noted %s)\n') % (
     411              left_margin * u' ',
     412              allergy['descriptor'],
     413              gmTools.coalesce(allergy['reaction'], _('unknown reaction')),
     414              gmDateTime.pydt_strftime(allergy['date'], '%Y %b %d')
     415          )
     416  #        txt = left_margin * ' ' \
     417  #           + _('Allergy') + ': ' \
     418  #            + allergy['descriptor'] + u', ' \
     419  #            + gmTools.coalesce(allergy['reaction'], _('unknown reaction')) ' ' \
     420  #            + _('(noted %s)') % gmDateTime.pydt_strftime(allergy['date'], '%Y %b %d') \
     421  #            + '\n'
     422          return txt
     423      #--------------------------------------------------------
     424 -    def get_vaccination_summary(self, vaccination, left_margin = 0):
     425          """
     426              Dumps vaccination item data summary
     427              vaccination - Vaccination item to dump
     428              left_margin - Number of spaces on the left margin
     429          """
     430          txt = left_margin*' ' + _('Vaccination') + ': ' + vaccination['l10n_indication'] + ', ' + \
     431              vaccination['narrative'] + '\n'
     432          return txt
     433      #--------------------------------------------------------
     434 -    def get_lab_result_summary(self, lab_result, left_margin = 0):
     435          """
     436              Dumps lab result item data summary
     437              lab_request - Lab request item to dump
     438              left_margin - Number of spaces on the left margin
     439          """
     440          txt = ''
     441          if self.lab_new_encounter:
     442              txt += (left_margin+3)*' ' + _('Lab') + ': '  + \
     443                  lab_result['unified_name'] + '-> ' + lab_result['unified_val'] + \
     444                  ' ' + lab_result['val_unit']+ '\n' + '(' + lab_result['req_when'].strftime('%Y-%m-%d') + ')'
     445          return txt
     446      #--------------------------------------------------------
     447 -    def get_item_summary(self, item, left_margin = 0):
     448          """
     449              Obtains formatted clinical item summary dump
     450              item - The clinical item to dump
     451              left_margin - Number of spaces on the left margin
     452          """
     453          txt = ''
     454          if isinstance(item, gmAllergy.cAllergy):
     455              txt += self.get_allergy_summary(item, left_margin)
     456  #        elif isinstance(item, gmVaccination.cVaccination):
     457   #           txt += self.get_vaccination_summary(item, left_margin)
     458  #        elif isinstance(item, gmPathLab.cLabResult) and \
     459  #           True:
     460   #           #(item['relevant'] == True or item['abnormal'] == True):
     461    #          txt += self.get_lab_result_summary(item, left_margin)
     462     #         self.lab_new_encounter = False
     463          return txt
     464      #--------------------------------------------------------
     465 -    def refresh_historical_tree(self, emr_tree):
     466          """
     467          checks a emr_tree constructed with this.get_historical_tree()
     468          and sees if any new items need to be inserted.
     469          """
     470          #TODO , caching eliminates tree update time, so don't really need this
     471          self._traverse_health_issues( emr_tree, self._update_health_issue_branch)
     472      #--------------------------------------------------------
     473 -    def get_historical_tree( self, emr_tree):
     474          self._traverse_health_issues( emr_tree, self._add_health_issue_branch)
     475      #--------------------------------------------------------
     476 -    def _traverse_health_issues(self, emr_tree, health_issue_action):
     477          """
     478          Retrieves patient's historical in form of a wx tree of health issues
     479                                                                                          -> episodes
     480                                                                                             -> encounters
     481          Encounter object is associated with item to allow displaying its information
     482          """
     483          # variable initialization
     484          # this protects the emrBrowser from locking up in a paint event, e.g. in
     485          # some configurations which want to use emrBrowser, but don't stop tabbing
     486          # to emr browser when no patient selected. the effect is to displace a cNull instance
     487          # which is a sane representation when no patient is selected.
     488          if not self.__fetch_filtered_items():
     489              return
     490          emr = self.__patient.get_emr()
     491          unlinked_episodes = emr.get_episodes(issues = [None])
     492          h_issues = []
     493          h_issues.extend(emr.get_health_issues(id_list = self.__constraints['issues']))
     494          # build the tree
     495          # unlinked episodes
     496          if len(unlinked_episodes) > 0:
     497              h_issues.insert(0, {
     498                  'description': _('Unattributed episodes'),
     499                  'pk_health_issue': None
     500              })
     501          # existing issues
     502          for a_health_issue in h_issues:
     503              health_issue_action( emr_tree, a_health_issue)
     504
     505          root_item = emr_tree.GetRootItem()
     506          if len(h_issues) == 0:
     507              emr_tree.SetItemHasChildren(root_item, False)
     508          else:
     509              emr_tree.SetItemHasChildren(root_item, True)
     510          emr_tree.SortChildren(root_item)
     511      #--------------------------------------------------------
     512 -    def _add_health_issue_branch( self, emr_tree, a_health_issue):
     513              """appends to a wx emr_tree  , building wx treenodes from the health_issue  make this reusable for non-collapsing tree updates"""
     514              emr = self.__patient.get_emr()
     515              root_node = emr_tree.GetRootItem()
     516              issue_node =  emr_tree.AppendItem(root_node, a_health_issue['description'])
     517              emr_tree.SetItemPyData(issue_node, a_health_issue)
     518              episodes = emr.get_episodes(id_list=self.__constraints['episodes'], issues = [a_health_issue['pk_health_issue']])
     519              if len(episodes) == 0:
     520                  emr_tree.SetItemHasChildren(issue_node, False)
     521              else:
     522                  emr_tree.SetItemHasChildren(issue_node, True)
     523              for an_episode in episodes:
     524                  self._add_episode_to_tree( emr, emr_tree, issue_node,a_health_issue,  an_episode)
     525              emr_tree.SortChildren(issue_node)
     526      #--------------------------------------------------------
     527 -    def _add_episode_to_tree( self, emr , emr_tree, issue_node, a_health_issue, an_episode):
     528          episode_node =  emr_tree.AppendItem(issue_node, an_episode['description'])
     529          emr_tree.SetItemPyData(episode_node, an_episode)
     530          if an_episode['episode_open']:
     531              emr_tree.SetItemBold(issue_node, True)
     532
     533          encounters = self._get_encounters( an_episode, emr )
     534          if len(encounters) == 0:
     535              emr_tree.SetItemHasChildren(episode_node, False)
     536          else:
     537              emr_tree.SetItemHasChildren(episode_node, True)
     538          self._add_encounters_to_tree( encounters,  emr_tree, episode_node )
     539          emr_tree.SortChildren(episode_node)
     540          return episode_node
     541      #--------------------------------------------------------
     542 -    def _add_encounters_to_tree( self, encounters, emr_tree, episode_node):
     543          for an_encounter in encounters:
     544  #            label = u'%s: %s' % (an_encounter['started'].strftime('%Y-%m-%d'), an_encounter['l10n_type'])
     545              label = u'%s: %s' % (
     546                  an_encounter['started'].strftime('%Y-%m-%d'),
     547                                  gmTools.unwrap (
     548                          gmTools.coalesce (
     549                          gmTools.coalesce (
     550                              gmTools.coalesce (
     551                                  an_encounter.get_latest_soap (                                          # soAp
     552                                      soap_cat = 'a',
     553                                      episode = emr_tree.GetPyData(episode_node)['pk_episode']
     554                                  ),
     555                                  an_encounter['assessment_of_encounter']                         # or AOE
     556                              ),
     557                              an_encounter['reason_for_encounter']                                        # or RFE
     558                          ),
     559                          an_encounter['l10n_type']                                                                       # or type
     560                          ),
     561                          max_length = 40
     562                  )
     563              )
     564              encounter_node_id = emr_tree.AppendItem(episode_node, label)
     565              emr_tree.SetItemPyData(encounter_node_id, an_encounter)
     566              emr_tree.SetItemHasChildren(encounter_node_id, False)
     567      #--------------------------------------------------------
     568 -    def _get_encounters ( self, an_episode, emr ):
     569                 encounters = emr.get_encounters (
     570                     episodes = [an_episode['pk_episode']]
     571                 )
     572                 return encounters
     573      #--------------------------------------------------------
     574 -    def  _update_health_issue_branch(self, emr_tree, a_health_issue):
     575                  emr = self.__patient.get_emr()
     576                  root_node = emr_tree.GetRootItem()
     577                  id, cookie = emr_tree.GetFirstChild(root_node)
     578                  found = False
     579                  while id.IsOk():
     580                          if emr_tree.GetItemText(id)  ==  a_health_issue['description']:
     581                                  found = True
     582                                  break
     583                          id,cookie = emr_tree.GetNextChild( root_node, cookie)
     584
     585                  if not found:
     586                          _log.error("health issue %s should exist in tree already", a_health_issue['description'] )
     587                          return
     588                  issue_node = id
     589                  episodes = emr.get_episodes(id_list=self.__constraints['episodes'], issues = [a_health_issue['pk_health_issue']])
     590
     591                  #check for removed episode and update tree
     592                  tree_episodes = {}
     593                  id_episode, cookie = emr_tree.GetFirstChild(issue_node)
     594                  while id_episode.IsOk():
     595                          tree_episodes[ emr_tree.GetPyData(id_episode)['pk_episode'] ]= id_episode
     596                          id_episode,cookie = emr_tree.GetNextChild( issue_node, cookie)
     597
     598                  existing_episode_pk = [ e['pk_episode'] for e in episodes]
     599                  missing_tree_pk = [ pk for pk in tree_episodes.keys() if pk not in existing_episode_pk]
     600                  for pk in missing_tree_pk:
     601                          emr_tree.Remove( tree_episodes[pk] )
     602
     603                  added_episode_pk = [pk for pk in existing_episode_pk if pk not in tree_episodes.keys()]
     604                  add_episodes = [ e for e in episodes if e['pk_episode'] in added_episode_pk]
     605
     606                  #check for added episodes and update tree
     607                  for an_episode in add_episodes:
     608                          node = self._add_episode_to_tree( emr, emr_tree, issue_node, a_health_issue, an_episode)
     609                          tree_episodes[an_episode['pk_episode']] = node
     610
     611                  for an_episode in episodes:
     612                          # found episode, check for encounter change
     613                          try:
     614                                  #print "getting id_episode of ", an_episode['pk_episode']
     615                                  id_episode = tree_episodes[an_episode['pk_episode']]
     616                          except:
     617                                  import pdb
     618                                  pdb.set_trace()
     619                          # get a map of encounters in the tree by pk_encounter as key
     620                          tree_enc = {}
     621                          id_encounter, cookie = emr_tree.GetFirstChild(id_episode)
     622                          while id_encounter.IsOk():
     623                                  tree_enc[ emr_tree.GetPyData(id_encounter)['pk_encounter'] ] = id_encounter
     624                                  id_encounter,cookie = emr_tree.GetNextChild(id_episode, cookie)
     625
     626                          # remove encounters in tree not in existing encounters in episode
     627  #                       encounters = self._get_encounters( a_health_issue, an_episode, emr )
     628                          encounters = self._get_encounters( an_episode, emr )
     629                          existing_enc_pk = [ enc['pk_encounter'] for enc in encounters]
     630                          missing_enc_pk = [ pk  for pk in tree_enc.keys() if pk not in existing_enc_pk]
     631                          for pk in missing_enc_pk:
     632                                  emr_tree.Remove( tree_enc[pk] )
     633
     634                          # check for added encounter
     635                          added_enc_pk = [ pk for pk in existing_enc_pk if pk not in tree_enc.keys() ]
     636                          add_encounters = [ enc for enc in encounters if enc['pk_encounter'] in added_enc_pk]
     637                          if add_encounters != []:
     638                                  #print "DEBUG found encounters to add"
     639                                  self._add_encounters_to_tree( add_encounters, emr_tree, id_episode)
     640      #--------------------------------------------------------
     641 -    def get_summary_info(self, left_margin = 0):
     642          """
     643          Dumps patient EMR summary
     644          """
     645          txt = ''
     646          for an_item in self.__filtered_items:
     647              txt += self.get_item_summary(an_item, left_margin)
     648          return txt
     649      #--------------------------------------------------------
     650 -    def get_episode_summary (self, episode, left_margin = 0):
     651          """Dumps episode specific data"""
     652          emr = self.__patient.get_emr()
     653          encs = emr.get_encounters(episodes = [episode['pk_episode']])
     654          if encs is None:
     655              txt = left_margin * ' ' + _('Error retrieving encounters for episode\n%s') % str(episode)
     656              return txt
     657          no_encs = len(encs)
     658          if no_encs == 0:
     659              txt = left_margin * ' ' + _('There are no encounters for this episode.')
     660              return txt
     661          if episode['episode_open']:
     662              status = _('active')
     663          else:
     664              status = _('finished')
     665          first_encounter = emr.get_first_encounter(episode_id = episode['pk_episode'])
     666          last_encounter = emr.get_last_encounter(episode_id = episode['pk_episode'])
     667          txt = _(
     668              '%sEpisode "%s" [%s]\n'
     669              '%sEncounters: %s (%s - %s)\n'
     670              '%sLast worked on: %s\n'
     671          ) % (
     672              left_margin * ' ', episode['description'], status,
     673              left_margin * ' ', no_encs, first_encounter['started'].strftime('%m/%Y'), last_encounter['last_affirmed'].strftime('%m/%Y'),
     674              left_margin * ' ', last_encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M')
     675          )
     676          return txt
     677      #--------------------------------------------------------
     678 -    def get_encounter_info(self, episode, encounter, left_margin = 0):
     679          """
     680          Dumps encounter specific data (rfe, aoe and soap)
     681          """
     682          emr = self.__patient.get_emr()
     683          # general
     684          txt = (' ' * left_margin) + '#%s: %s - %s   %s' % (
     685              encounter['pk_encounter'],
     686              encounter['started'].strftime('%Y-%m-%d %H:%M'),
     687              encounter['last_affirmed'].strftime('%H:%M (%Z)'),
     688              encounter['l10n_type']
     689          )
     690          if (encounter['assessment_of_encounter'] is not None) and (len(encounter['assessment_of_encounter']) > 0):
     691              txt += ' "%s"' % encounter['assessment_of_encounter']
     692          txt += '\n\n'
     693
     694          # rfe/aoe
     695          txt += (' ' * left_margin) + '%s: %s\n' % (_('RFE'), encounter['reason_for_encounter'])
     696          txt += (' ' * left_margin) + '%s: %s\n' % (_('AOE'), encounter['assessment_of_encounter'])
     697
     698          # soap
     699          soap_cat_labels = {
     700              's': _('Subjective'),
     701              'o': _('Objective'),
     702              'a': _('Assessment'),
     703              'p': _('Plan'),
     704              None: _('Administrative')
     705          }
     706          eol_w_margin = '\n' + (' ' * (left_margin+3))
     707          for soap_cat in 'soapu':
     708              soap_cat_narratives = emr.get_clin_narrative (
     709                  episodes = [episode['pk_episode']],
     710                  encounters = [encounter['pk_encounter']],
     711                  soap_cats = [soap_cat]
     712              )
     713              if soap_cat_narratives is None:
     714                  continue
     715              if len(soap_cat_narratives) == 0:
     716                  continue
     717              txt += (' ' * left_margin) + soap_cat_labels[soap_cat] + ':\n'
     718              for soap_entry in soap_cat_narratives:
     719                  txt += gmTools.wrap (
     720                      '%s %.8s: %s\n' % (
     721                          soap_entry['date'].strftime('%d.%m. %H:%M'),
     722                          soap_entry['modified_by'],
     723                          soap_entry['narrative']
     724                      ), 75
     725                  )
     726
     727  #                txt += (
     728   #                   (' ' * (left_margin+3)) +
     729    #                  soap_entry['date'].strftime('%H:%M %.8s: ') % soap_entry['modified_by'] +
     730     #                 soap_entry['narrative'].replace('\n', eol_w_margin) +
     731      #                '\n'
     732       #           )
     733                  #FIXME: add diagnoses
     734
     735          # items
     736          for an_item in self.__filtered_items:
     737              if an_item['pk_encounter'] == encounter['pk_encounter']:
     738                  txt += self.get_item_output(an_item, left_margin)
     739          return txt
     740      #--------------------------------------------------------
     741 -    def dump_historical_tree(self):
     742          """Dumps patient's historical in form of a tree of health issues
     743                                                          -> episodes
     744                                                             -> encounters
     745                                                                -> clinical items
     746          """
     747
     748          # fecth all values
     749          self.__fetch_filtered_items()
     750          emr = self.__patient.get_emr()
     751
     752          # dump clinically relevant items summary
     753          for an_item in self.__filtered_items:
     754              self.__target.write(self.get_item_summary(an_item, 3))
     755
     756          # begin with the tree
     757          h_issues = []
     758          h_issues.extend(emr.get_health_issues(id_list = self.__constraints['issues']))
     759          # unlinked episodes
     760          unlinked_episodes = emr.get_episodes(issues = [None])
     761          if len(unlinked_episodes) > 0:
     762              h_issues.insert(0, {'description':_('Unattributed episodes'), 'pk_health_issue':None})
     763          for a_health_issue in h_issues:
     764              self.__target.write('\n' + 3*' ' + 'Health Issue: ' + a_health_issue['description'] + '\n')
     765              episodes = emr.get_episodes(id_list=self.__constraints['episodes'], issues = [a_health_issue['pk_health_issue']])
     766              for an_episode in episodes:
     767                 self.__target.write('\n' + 6*' ' + 'Episode: ' + an_episode['description'] + '\n')
     768                 if a_health_issue['pk_health_issue'] is None:
     769                    issues = None
     770                 else:
     771                    issues = [a_health_issue['pk_health_issue']]
     772                 encounters = emr.get_encounters (
     773                    since = self.__constraints['since'],
     774                    until = self.__constraints['until'],
     775                    id_list = self.__constraints['encounters'],
     776                    episodes = [an_episode['pk_episode']],
     777                    issues = issues
     778                 )
     779                 for an_encounter in encounters:
     780                      # title
     781                      self.lab_new_encounter = True
     782                      self.__target.write(
     783                          '\n            %s %s: %s - %s (%s)\n' % (
     784                              _('Encounter'),
     785                              an_encounter['l10n_type'],
     786                              an_encounter['started'].strftime('%A, %Y-%m-%d %H:%M'),
     787                              an_encounter['last_affirmed'].strftime('%m-%d %H:%M'),
     788                              an_encounter['assessment_of_encounter']
     789                          )
     790                      )
     791                      self.__target.write(self.get_encounter_info(an_episode, an_encounter, 12))
     792      #--------------------------------------------------------
     793 -    def dump_clinical_record(self):
     794          """
     795          Dumps in ASCII format patient's clinical record
     796          """
     797          emr = self.__patient.get_emr()
     798          if emr is None:
     799              _log.error('cannot get EMR text dump')
     800              print(_(
     801                  'An error occurred while retrieving a text\n'
     802                  'dump of the EMR for the active patient.\n\n'
     803                  'Please check the log file for details.'
     804              ))
     805              return None
     806          self.__target.write('\nOverview\n')
     807          self.__target.write('--------\n')
     808          self.__target.write("1) Allergy status (for details, see below):\n\n")
     809          for allergy in       emr.get_allergies():
     810              self.__target.write("    " + allergy['descriptor'] + "\n\n")
     811          self.__target.write("2) Vaccination status (* indicates booster):\n")
     812  #        self.get_vacc_table()
     813          self.__target.write("\n3) Historical:\n\n")
     814          self.dump_historical_tree()
     815
     816          try:
     817              emr.cleanup()
     818          except:
     819              print "error cleaning up EMR"
     820      #--------------------------------------------------------
     821 -    def dump_med_docs(self):
     822          """
     823              Dumps patient stored medical documents
     824
     825          """
     826          doc_folder = self.__patient.get_document_folder()
     827
     828          self.__target.write('\n4) Medical documents: (date) reference - type "comment"\n')
     829          self.__target.write('                          object - comment')
     830
     831          docs = doc_folder.get_documents()
     832          for doc in docs:
     833              self.__target.write('\n\n    (%s) %s - %s "%s"' % (
     834                  doc['clin_when'].strftime('%Y-%m-%d'),
     835                  doc['ext_ref'],
     836                  doc['l10n_type'],
     837                  doc['comment'])
     838              )
     839              for part in doc.parts:
     840                  self.__target.write('\n         %s - %s' % (
     841                      part['seq_idx'],
     842                      part['obj_comment'])
     843                  )
     844          self.__target.write('\n\n')
     845      #--------------------------------------------------------
     846 -    def dump_demographic_record(self, all = False):
     847          """
     848              Dumps in ASCII format some basic patient's demographic data
     849          """
     850          if self.__patient is None:
     851              _log.error('cannot get Demographic export')
     852              print(_(
     853                  'An error occurred while Demographic record export\n'
     854                  'Please check the log file for details.'
     855              ))
     856              return None
     857
     858          self.__target.write('\n\n\nDemographics')
     859          self.__target.write('\n------------\n')
     860          self.__target.write('    Id: %s \n' % self.__patient['pk_identity'])
     861          cont = 0
     862          for name in self.__patient.get_names():
     863              if cont == 0:
     864                  self.__target.write('    Name (Active): %s, %s\n' % (name['firstnames'], name['lastnames']) )
     865              else:
     866                  self.__target.write('    Name %s: %s, %s\n' % (cont, name['firstnames'], name['lastnames']))
     867              cont += 1
     868          self.__target.write('    Gender: %s\n' % self.__patient['gender'])
     869          self.__target.write('    Title: %s\n' % self.__patient['title'])
     870          self.__target.write('    Dob: %s\n' % self.__patient.get_formatted_dob(format = '%Y-%m-%d'))
     871          self.__target.write('    Medical age: %s\n' % self.__patient.get_medical_age())
     872      #--------------------------------------------------------
     873 -    def dump_constraints(self):
     874          """
     875              Dumps exporter filtering constraints
     876          """
     877          self.__first_constraint = True
     878          if not self.__constraints['since'] is None:
     879              self.dump_constraints_header()
     880              self.__target.write('\nSince: %s' % self.__constraints['since'].strftime('%Y-%m-%d'))
     881
     882          if not self.__constraints['until'] is None:
     883              self.dump_constraints_header()
     884              self.__target.write('\nUntil: %s' % self.__constraints['until'].strftime('%Y-%m-%d'))
     885
     886          if not self.__constraints['encounters'] is None:
     887              self.dump_constraints_header()
     888              self.__target.write('\nEncounters: ')
     889              for enc in self.__constraints['encounters']:
     890                  self.__target.write(str(enc) + ' ')
     891
     892          if not self.__constraints['episodes'] is None:
     893              self.dump_constraints_header()
     894              self.__target.write('\nEpisodes: ')
     895              for epi in self.__constraints['episodes']:
     896                  self.__target.write(str(epi) + ' ')
     897
     898          if not self.__constraints['issues'] is None:
     899              self.dump_constraints_header()
     900              self.__target.write('\nIssues: ')
     901              for iss in self.__constraints['issues']:
     902                  self.__target.write(str(iss) + ' ')
     903      #--------------------------------------------------------
     904 -    def dump_constraints_header(self):
     905          """
     906              Dumps constraints header
     907          """
     908          if self.__first_constraint == True:
     909              self.__target.write('\nClinical items dump constraints\n')
     910              self.__target.write('-'*(len(head_txt)-2))
     911              self.__first_constraint = False
     912  #============================================================
     913 -class cEMRJournalExporter:
     914          """Exports patient EMR into a simple chronological journal.
     915
     916          Note that this export will emit u'' strings only.
     917          """
     918 -        def __init__(self):
     919                  self.__part_len = 72
     920          #--------------------------------------------------------
     921          # external API
     922          #--------------------------------------------------------
     923 -        def export_to_file_by_mod_time(self, filename=None, patient=None):
     924                  if patient is None:
     925                          patient = gmPerson.gmCurrentPatient()
     926                          if not patient.connected:
     927                                  raise ValueError('[%s].export_to_file(): no active patient' % self.__class__.__name__)
     928
     929                  if filename is None:
     930                          filename = gmTools.get_unique_filename(prefix = 'gm-emr_by_mod_time-', suffix = '.txt')
     931
     932                  f = codecs.open(filename = filename, mode = 'w+b', encoding = 'utf8', errors = 'replace')
     933
     934                  self.__part_len = 80
     935
     936                  # write header
     937                  txt = _('EMR Journal sorted by last modification time\n')
     938                  f.write(txt)
     939                  f.write(u'=' * (len(txt)-1))
     940                  f.write('\n')
     941                  f.write(_('Patient: %s (%s), No: %s\n') % (patient['description'], patient['gender'], patient['pk_identity']))
     942                  f.write(_('Born   : %s, age: %s\n\n') % (
     943                          patient.get_formatted_dob(format = '%Y %b %d', encoding = gmI18N.get_encoding()),
     944                          patient.get_medical_age()
     945                  ))
     946
     947                  # get data
     948                  cmd = u"""
     949                          SELECT
     950                                  vemrj.*,
     951                                  to_char(vemrj.modified_when, 'YYYY-MM-DD HH24:MI') AS date_modified
     952                          FROM clin.v_emr_journal vemrj
     953                          WHERE pk_patient = %(pat)s
     954                          ORDER BY modified_when
     955                  """
     956                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': patient['pk_identity']}}])
     957
     958                  f.write ((u'-' * 100) + u'\n')
     959                  f.write (u'%16.16s | %9.9s | %1.1s | %s \n' % (_('Last modified'), _('By'), u' ', _('Entry')))
     960                  f.write ((u'-' * 100) + u'\n')
     961
     962                  for r in rows:
     963                          txt = u'%16.16s | %9.9s | %1.1s | %s \n' % (
     964                                  r['date_modified'],
     965                                  r['modified_by'],
     966                                  gmClinNarrative.soap_cat2l10n[r['soap_cat']],
     967                                  gmTools.wrap (
     968                                          text = r['narrative'].replace(u'\r', u'') + u' (%s: %s)' % (_('When'), gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M')),
     969                                          width = self.__part_len,
     970                                          subsequent_indent = u'%31.31s%1.1s | ' % (u' ', gmClinNarrative.soap_cat2l10n[r['soap_cat']])
     971                                  )
     972                          )
     973                          f.write(txt)
     974
     975                  f.write((u'-' * 100) + u'\n\n')
     976                  f.write(_('Exported: %s\n') % gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), '%Y %b %d  %H:%M:%S'))
     977
     978                  f.close()
     979                  return filename
     980          #--------------------------------------------------------
     981 -        def export_to_file(self, filename=None, patient=None):
     982                  """Export medical record into a file.
     983
     984                  @type filename: None (creates filename by itself) or string
     985                  @type patient: None (use currently active patient) or <gmPerson.cIdentity> instance
     986                  """
     987                  if patient is None:
     988                          patient = gmPerson.gmCurrentPatient()
     989                          if not patient.connected:
     990                                  raise ValueError('[%s].export_to_file(): no active patient' % self.__class__.__name__)
     991
     992                  if filename is None:
     993                          filename = u'%s-%s-%s-%s.txt' % (
     994                                  _('emr-journal'),
     995                                  patient['lastnames'].replace(u' ', u'_'),
     996                                  patient['firstnames'].replace(u' ', u'_'),
     997                                  patient.get_formatted_dob(format = '%Y-%m-%d')
     998                          )
     999                          path = os.path.expanduser(os.path.join('~', 'gnumed', patient['dirname'], filename))
    1000
    1001                  f = codecs.open(filename = filename, mode = 'w+b', encoding = 'utf8', errors = 'replace')
    1002                  self.export(target = f, patient = patient)
    1003                  f.close()
    1004                  return filename
    1005          #--------------------------------------------------------
    1006          # internal API
    1007          #--------------------------------------------------------
    1008 -        def export(self, target=None, patient=None):
    1009                  """
    1010                  Export medical record into a Python object.
    1011
    1012                  @type target: a python object supporting the write() API
    1013                  @type patient: None (use currently active patient) or <gmPerson.cIdentity> instance
    1014                  """
    1015                  if patient is None:
    1016                          patient = gmPerson.gmCurrentPatient()
    1017                          if not patient.connected:
    1018                                  raise ValueError('[%s].export(): no active patient' % self.__class__.__name__)
    1019
    1020                  # write header
    1021                  txt = _('Chronological EMR Journal\n')
    1022                  target.write(txt)
    1023                  target.write(u'=' * (len(txt)-1))
    1024                  target.write('\n')
    1025                  target.write(_('Patient: %s (%s), No: %s\n') % (patient['description'], patient['gender'], patient['pk_identity']))
    1026                  target.write(_('Born   : %s, age: %s\n\n') % (
    1027                          patient.get_formatted_dob(format = '%Y %b %d', encoding = gmI18N.get_encoding()),
    1028                          patient.get_medical_age()
    1029                  ))
    1030                  target.write(u'.-%10.10s---%9.9s-------%72.72s\n' % (u'-' * 10, u'-' * 9, u'-' * self.__part_len))
    1031                  target.write(u'| %10.10s | %9.9s |     | %s\n' % (_('Encounter'), _('Doc'), _('Narrative')))
    1032                  target.write(u'|-%10.10s---%9.9s-------%72.72s\n' % (u'-' * 10, u'-' * 9, u'-' * self.__part_len))
    1033
    1034                  # get data
    1035                  cmd = u"""
    1036                          SELECT
    1037                                  to_char(vemrj.clin_when, 'YYYY-MM-DD') AS date,
    1038                                  vemrj.*,
    1039                                  (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = vemrj.soap_cat) AS scr,
    1040                                  to_char(vemrj.modified_when, 'YYYY-MM-DD HH24:MI') AS date_modified
    1041                          FROM clin.v_emr_journal vemrj
    1042                          WHERE pk_patient = %s
    1043                          ORDER BY date, pk_episode, scr, src_table
    1044                  """
    1045                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [patient['pk_identity']]}], get_col_idx = True)
    1046
    1047                  # write data
    1048                  prev_date = u''
    1049                  prev_doc = u''
    1050                  prev_soap = u''
    1051                  for row in rows:
    1052                          # narrative
    1053                          if row['narrative'] is None:
    1054                                  continue
    1055
    1056                          txt = gmTools.wrap (
    1057                                  text = row['narrative'].replace(u'\r', u'') + (u' (%s)' % row['date_modified']),
    1058                                  width = self.__part_len
    1059                          ).split('\n')
    1060
    1061                          # same provider ?
    1062                          curr_doc = row['modified_by']
    1063                          if curr_doc != prev_doc:
    1064                                  prev_doc = curr_doc
    1065                          else:
    1066                                  curr_doc = u''
    1067
    1068                          # same soap category ?
    1069                          curr_soap = row['soap_cat']
    1070                          if curr_soap != prev_soap:
    1071                                  prev_soap = curr_soap
    1072
    1073                          # same date ?
    1074                          curr_date = row['date']
    1075                          if curr_date != prev_date:
    1076                                  prev_date = curr_date
    1077                                  curr_doc = row['modified_by']
    1078                                  prev_doc = curr_doc
    1079                                  curr_soap = row['soap_cat']
    1080                                  prev_soap = curr_soap
    1081                          else:
    1082                                  curr_date = u''
    1083
    1084                          # display first part
    1085                          target.write(u'| %10.10s | %9.9s | %3.3s | %s\n' % (
    1086                                  curr_date,
    1087                                  curr_doc,
    1088                                  gmClinNarrative.soap_cat2l10n[curr_soap],
    1089                                  txt[0]
    1090                          ))
    1091
    1092                          # only one part ?
    1093                          if len(txt) == 1:
    1094                                  continue
    1095
    1096                          template = u'| %10.10s | %9.9s | %3.3s | %s\n'
    1097                          for part in txt[1:]:
    1098                                  line = template % (u'', u'', u' ', part)
    1099                                  target.write(line)
    1100
    1101                  # write footer
    1102                  target.write(u'`-%10.10s---%9.9s-------%72.72s\n\n' % (u'-' * 10, u'-' * 9, u'-' * self.__part_len))
    1103                  target.write(_('Exported: %s\n') % gmDateTime.pydt_strftime(pyDT.datetime.now(), '%Y %b %d  %H:%M:%S'))
    1104
    1105                  return
    1106  #============================================================
    1107 -class cMedistarSOAPExporter:
    1108          """Export SOAP data per encounter into Medistar import format."""
    1109 -        def __init__(self, patient=None):
    1110                  if patient is None:
    1111                          self.__pat = gmPerson.gmCurrentPatient()
    1112                  else:
    1113                          if not isinstance(patient, gmPerson.cIdentity):
    1114                                  raise gmExceptions.ConstructorError, '<patient> argument must be instance of <cIdentity>, but is: %s' % type(patient)
    1115                          self.__pat = patient
    1116          #--------------------------------------------------------
    1117          # external API
    1118          #--------------------------------------------------------
    1119 -        def export_to_file(self, filename=None, encounter=None, soap_cats=u'soapu', export_to_import_file=False):
    1120                  if not self.__pat.connected:
    1121                          return (False, 'no active patient')
    1122
    1123                  if filename is None:
    1124                          path = os.path.abspath(os.path.expanduser('~/gnumed/export'))
    1125                          filename = '%s-%s-%s-%s-%s.txt' % (
    1126                                  os.path.join(path, 'Medistar-MD'),
    1127                                  time.strftime('%Y-%m-%d',time.localtime()),
    1128                                  self.__pat['lastnames'].replace(' ', '-'),
    1129                                  self.__pat['firstnames'].replace(' ', '_'),
    1130                                  self.__pat.get_formatted_dob(format = '%Y-%m-%d')
    1131                          )
    1132
    1133                  f = codecs.open(filename = filename, mode = 'w+b', encoding = 'cp437', errors='replace')
    1134                  status = self.__export(target = f, encounter = encounter, soap_cats = soap_cats)
    1135                  f.close()
    1136
    1137                  if export_to_import_file:
    1138                          # detect "LW:\medistar\inst\soap.txt"
    1139                          medistar_found = False
    1140                          for drive in u'cdefghijklmnopqrstuvwxyz':
    1141                                  path = drive + ':\\medistar\\inst'
    1142                                  if not os.path.isdir(path):
    1143                                          continue
    1144                                  try:
    1145                                          import_fname = path + '\\soap.txt'
    1146                                          open(import_fname, mode = 'w+b').close()
    1147                                          _log.debug('exporting narrative to [%s] for Medistar import', import_fname)
    1148                                          shutil.copyfile(filename, import_fname)
    1149                                          medistar_found = True
    1150                                  except IOError:
    1151                                          continue
    1152
    1153                          if not medistar_found:
    1154                                  _log.debug('no Medistar installation found (no <LW>:\\medistar\\inst\\)')
    1155
    1156                  return (status, filename)
    1157          #--------------------------------------------------------
    1158 -        def export(self, target, encounter=None, soap_cats=u'soapu'):
    1159                  return self.__export(target, encounter = encounter, soap_cats = soap_cats)
    1160          #--------------------------------------------------------
    1161          # interal API
    1162          #--------------------------------------------------------
    1163 -        def __export(self, target=None, encounter=None, soap_cats=u'soapu'):
    1164                  # get data
    1165                  cmd = u"select narrative from clin.v_emr_journal where pk_patient=%s and pk_encounter=%s and soap_cat=%s"
    1166                  for soap_cat in soap_cats:
    1167                          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': (self.__pat['pk_identity'], encounter['pk_encounter'], soap_cat)}])
    1168                          target.write('*MD%s*\r\n' % gmClinNarrative.soap_cat2l10n[soap_cat])
    1169                          for row in rows:
    1170                                  text = row[0]
    1171                                  if text is None:
    1172                                          continue
    1173                                  target.write('%s\r\n' % gmTools.wrap (
    1174                                          text = text,
    1175                                          width = 64,
    1176                                          eol = u'\r\n'
    1177                                  ))
    1178                  return True
    1179  #============================================================
    1180  # main
    1181  #------------------------------------------------------------
    1182 -def usage():
    1183      """
    1184          Prints application usage options to stdout.
    1185      """
    1186      print 'usage: python gmPatientExporter [--fileout=<outputfilename>] [--conf-file=<file>] [--text-domain=<textdomain>]'
    1187      sys.exit(0)
    1188  #------------------------------------------------------------
    1189 -def run():
    1190      """
    1191          Main module application execution loop.
    1192      """
    1193      # More variable initializations
    1194      patient = None
    1195      patient_id = None
    1196      patient_term = None
    1197      pat_searcher = gmPersonSearch.cPatientSearcher_SQL()
    1198
    1199      # App execution loop
    1200      while patient_term != 'bye':
    1201          patient = gmPersonSearch.ask_for_patient()
    1202          if patient is None:
    1203              break
    1204          # FIXME: needed ?
    1205  #        gmPerson.set_active_patient(patient=patient)
    1206          exporter = cEMRJournalExporter()
    1207          exporter.export_to_file(patient=patient)
    1208  #        export_tool.set_patient(patient)
    1209          # Dump patient EMR sections
    1210  #        export_tool.dump_constraints()
    1211  #        export_tool.dump_demographic_record(True)
    1212  #        export_tool.dump_clinical_record()
    1213  #        export_tool.dump_med_docs()
    1214
    1215      # Clean ups
    1216  #    outFile.close()
    1217  #    export_tool.cleanup()
    1218      if patient is not None:
    1219          try:
    1220              patient.cleanup()
    1221          except:
    1222              print "error cleaning up patient"
    1223  #============================================================
    1224  # main
    1225  #------------------------------------------------------------
    1226  if __name__ == "__main__":
    1227          gmI18N.activate_locale()
    1228          gmI18N.install_domain()
    1229
    1230          #--------------------------------------------------------
    1231 -        def export_journal():
    1232
    1233                  print "Exporting EMR journal(s) ..."
    1234                  pat_searcher = gmPersonSearch.cPatientSearcher_SQL()
    1235                  while True:
    1236                          patient = gmPersonSearch.ask_for_patient()
    1237                          if patient is None:
    1238                                  break
    1239
    1240                          exporter = cEMRJournalExporter()
    1241                          print "exported into file:", exporter.export_to_file(patient=patient)
    1242
    1243                          if patient is not None:
    1244                                  try:
    1245                                          patient.cleanup()
    1246                                  except:
    1247                                          print "error cleaning up patient"
    1248                  print "Done."
    1249          #--------------------------------------------------------
    1250 -        def export_forensics():
    1251                  pat_searcher = gmPersonSearch.cPatientSearcher_SQL()
    1252                  patient = gmPersonSearch.ask_for_patient()
    1253                  if patient is None:
    1254                          return
    1255
    1256                  exporter = cEMRJournalExporter()
    1257                  print "exported into file:", exporter.export_to_file_by_mod_time(patient = patient)
    1258          #--------------------------------------------------------
    1259          print "\n\nGNUmed ASCII EMR Export"
    1260          print     "======================="
    1261
    1262          #export_journal()
    1263          export_forensics()
    1264
    1265  #============================================================
    1266