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