Gnumed.exporters.gmPatientExporter

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

# Source Code for Module Gnumed.exporters.gmPatientExporter

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