Gnumed.wxpython.gmEMRStructWidgets

Package Gnumed :: Package wxpython :: Module gmEMRStructWidgets |
[frames] | no frames]

# Source Code for Module Gnumed.wxpython.gmEMRStructWidgets

       1  """GNUmed EMR structure editors
       2
       3          This module contains widgets to create and edit EMR structural
       4          elements (issues, enconters, episodes).
       5
       6          This is based on initial work and ideas by Syan <kittylitter@swiftdsl.com.au>
       7          and Karsten <Karsten.Hilbert@gmx.net>.
       8  """
       9  #================================================================
      10  __author__ = "cfmoro1976@yahoo.es, karsten.hilbert@gmx.net"
      11  __license__ = "GPL v2 or later"
      12
      13  # stdlib
      14  import sys
      15  import time
      16  import logging
      17  import datetime as pydt
      18
      19
      20  # 3rd party
      21  import wx
      22
      23
      24  # GNUmed
      25  if __name__ == '__main__':
      26          sys.path.insert(0, '../../')
      27
      28  from Gnumed.pycommon import gmI18N
      29  from Gnumed.pycommon import gmDateTime
      30
      31  if __name__ == '__main__':
      32          gmI18N.activate_locale()
      33          gmI18N.install_domain()
      34          gmDateTime.init()
      35
      36  from Gnumed.pycommon import gmExceptions
      37  from Gnumed.pycommon import gmCfg
      38  from Gnumed.pycommon import gmTools
      39  from Gnumed.pycommon import gmDispatcher
      40  from Gnumed.pycommon import gmMatchProvider
      41
      42  from Gnumed.business import gmEMRStructItems
      43  from Gnumed.business import gmPraxis
      44  from Gnumed.business import gmPerson
      45
      46  from Gnumed.wxpython import gmPhraseWheel
      47  from Gnumed.wxpython import gmGuiHelpers
      48  from Gnumed.wxpython import gmListWidgets
      49  from Gnumed.wxpython import gmEditArea
      50
      51
      52  _log = logging.getLogger('gm.ui')
      53
      54  #================================================================
      55  # EMR access helper functions
      56  #----------------------------------------------------------------
      57 -def emr_access_spinner(time2spin=0):
      58          """Spin time in seconds but let wx go on."""
      59          if time2spin == 0:
      60                  return
      61          sleep_time = 0.1                        # 100ms
      62          total_rounds = int(time2spin / sleep_time)
      63          if total_rounds < 1:
      64                  wx.Yield()
      65                  time.sleep(sleep_time)
      66                  return
      67          rounds = 0
      68          while rounds < total_rounds:
      69                  wx.Yield()
      70                  time.sleep(sleep_time)
      71                  rounds += 1
      72
      73  #================================================================
      74  # episode related widgets/functions
      75  #----------------------------------------------------------------
      76 -def edit_episode(parent=None, episode=None):
      77          ea = cEpisodeEditAreaPnl(parent, -1)
      78          ea.data = episode
      79          ea.mode = gmTools.coalesce(episode, 'new', 'edit')
      80          dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = True)
      81          dlg.SetTitle(gmTools.coalesce(episode, _('Adding a new episode'), _('Editing an episode')))
      82          if dlg.ShowModal() == wx.ID_OK:
      83                  return True
      84          return False
      85
      86  #----------------------------------------------------------------
      87 -def manage_episodes(parent=None):
      88
      89          pat = gmPerson.gmCurrentPatient()
      90          emr = pat.emr
      91
      92          if parent is None:
      93                  parent = wx.GetApp().GetTopWindow()
      94          #-----------------------------------------
      95          def edit(episode=None):
      96                  return edit_episode(parent = parent, episode = episode)
      97          #-----------------------------------------
      98          def delete(episode=None):
      99                  if gmEMRStructItems.delete_episode(episode = episode):
     100                          return True
     101                  gmDispatcher.send (
     102                          signal = 'statustext',
     103                          msg = _('Cannot delete episode.'),
     104                          beep = True
     105                  )
     106                  return False
     107          #-----------------------------------------
     108          def manage_issues(episode=None):
     109                  return select_health_issues(parent = None, emr = emr)
     110          #-----------------------------------------
     111          def get_tooltip(data):
     112                  if data is None:
     113                          return None
     114                  return data.format (
     115                          patient = pat,
     116                          with_summary = True,
     117                          with_codes = True,
     118                          with_encounters = False,
     119                          with_documents = False,
     120                          with_hospital_stays = False,
     121                          with_procedures = False,
     122                          with_family_history = False,
     123                          with_tests = False,
     124                          with_vaccinations = False,
     125                          with_health_issue = True
     126                  )
     127          #-----------------------------------------
     128          def refresh(lctrl):
     129                  epis = emr.get_episodes(order_by = 'description')
     130                  items = [
     131                          [       e['description'],
     132                                  gmTools.bool2subst(e['episode_open'], _('ongoing'), _('closed'), '<unknown>'),
     133                                  gmDateTime.pydt_strftime(e.best_guess_clinical_start_date, '%Y %b %d'),
     134                                  gmTools.coalesce(e['health_issue'], '')
     135                          ] for e in epis
     136                  ]
     137                  lctrl.set_string_items(items = items)
     138                  lctrl.set_data(data = epis)
     139          #-----------------------------------------
     140          gmListWidgets.get_choices_from_list (
     141                  parent = parent,
     142                  msg = _('\nSelect the episode you want to edit !\n'),
     143                  caption = _('Editing episodes ...'),
     144                  columns = [_('Episode'), _('Status'), _('Started'), _('Health issue')],
     145                  single_selection = True,
     146                  edit_callback = edit,
     147                  new_callback = edit,
     148                  delete_callback = delete,
     149                  refresh_callback = refresh,
     150                  list_tooltip_callback = get_tooltip,
     151                  left_extra_button = (_('Manage issues'), _('Manage health issues'), manage_issues)
     152          )
     153
     154  #----------------------------------------------------------------
     155 -def promote_episode_to_issue(parent=None, episode=None, emr=None):
     156
     157          created_new_issue = False
     158
     159          try:
     160                  issue = gmEMRStructItems.cHealthIssue(name = episode['description'], patient = episode['pk_patient'])
     161          except gmExceptions.NoSuchBusinessObjectError:
     162                  issue = None
     163
     164          if issue is None:
     165                  issue = emr.add_health_issue(issue_name = episode['description'])
     166                  created_new_issue = True
     167          else:
     168                  # issue exists already, so ask user
     169                  dlg = gmGuiHelpers.c3ButtonQuestionDlg (
     170                          parent,
     171                          -1,
     172                          caption = _('Promoting episode to health issue'),
     173                          question = _(
     174                                  'There already is a health issue\n'
     175                                  '\n'
     176                                  ' %s\n'
     177                                  '\n'
     178                                  'What do you want to do ?'
     179                          ) % issue['description'],
     180                          button_defs = [
     181                                  {'label': _('Use existing'), 'tooltip': _('Move episode into existing health issue'), 'default': False},
     182                                  {'label': _('Create new'), 'tooltip': _('Create a new health issue with another name'), 'default': True}
     183                          ]
     184                  )
     185                  use_existing = dlg.ShowModal()
     186                  dlg.Destroy()
     187
     188                  if use_existing == wx.ID_CANCEL:
     189                          return
     190
     191                  # user wants to create new issue with alternate name
     192                  if use_existing == wx.ID_NO:
     193                          # loop until name modified but non-empty or cancelled
     194                          issue_name = episode['description']
     195                          while issue_name == episode['description']:
     196                                  dlg = wx.TextEntryDialog (
     197                                          parent = parent,
     198                                          message = _('Enter a short descriptive name for the new health issue:'),
     199                                          caption = _('Creating a new health issue ...'),
     200                                          defaultValue = issue_name,
     201                                          style = wx.OK | wx.CANCEL | wx.CENTRE
     202                                  )
     203                                  decision = dlg.ShowModal()
     204                                  if decision != wx.ID_OK:
     205                                          dlg.Destroy()
     206                                          return
     207                                  issue_name = dlg.GetValue().strip()
     208                                  dlg.Destroy()
     209                                  if issue_name == '':
     210                                          issue_name = episode['description']
     211
     212                          issue = emr.add_health_issue(issue_name = issue_name)
     213                          created_new_issue = True
     214
     215          # eventually move the episode to the issue
     216          if not move_episode_to_issue(episode = episode, target_issue = issue, save_to_backend = True):
     217                  # user cancelled the move so delete just-created issue
     218                  if created_new_issue:
     219                          # shouldn't fail as it is completely new
     220                          gmEMRStructItems.delete_health_issue(health_issue = issue)
     221                  return
     222
     223          return
     224
     225  #----------------------------------------------------------------
     226 -def move_episode_to_issue(episode=None, target_issue=None, save_to_backend=False):
     227          """Prepare changing health issue for an episode.
     228
     229          Checks for two-open-episodes conflict. When this
     230          function succeeds, the pk_health_issue has been set
     231          on the episode instance and the episode should - for
     232          all practical purposes - be ready for save_payload().
     233          """
     234          # episode is closed: should always work
     235          if not episode['episode_open']:
     236                  episode['pk_health_issue'] = target_issue['pk_health_issue']
     237                  if save_to_backend:
     238                          episode.save_payload()
     239                  return True
     240
     241          # un-associate: should always work, too
     242          if target_issue is None:
     243                  episode['pk_health_issue'] = None
     244                  if save_to_backend:
     245                          episode.save_payload()
     246                  return True
     247
     248          # try closing possibly expired episode on target issue if any
     249          db_cfg = gmCfg.cCfgSQL()
     250          epi_ttl = int(db_cfg.get2 (
     251                  option = 'episode.ttl',
     252                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     253                  bias = 'user',
     254                  default = 60                            # 2 months
     255          ))
     256          if target_issue.close_expired_episode(ttl=epi_ttl) is True:
     257                  gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
     258          existing_epi = target_issue.get_open_episode()
     259
     260          # no more open episode on target issue: should work now
     261          if existing_epi is None:
     262                  episode['pk_health_issue'] = target_issue['pk_health_issue']
     263                  if save_to_backend:
     264                          episode.save_payload()
     265                  return True
     266
     267          # don't conflict on SELF ;-)
     268          if existing_epi['pk_episode'] == episode['pk_episode']:
     269                  episode['pk_health_issue'] = target_issue['pk_health_issue']
     270                  if save_to_backend:
     271                          episode.save_payload()
     272                  return True
     273
     274          # we got two open episodes at once, ask user
     275          move_range = (episode.best_guess_clinical_start_date, episode.best_guess_clinical_end_date)
     276          if move_range[1] is None:
     277                  move_range_end = '?'
     278          else:
     279                  move_range_end = move_range[1].strftime('%m/%y')
     280          exist_range = (existing_epi.best_guess_clinical_start_date, existing_epi.best_guess_clinical_end_date)
     281          if exist_range[1] is None:
     282                  exist_range_end = '?'
     283          else:
     284                  exist_range_end = exist_range[1].strftime('%m/%y')
     285          question = _(
     286                  'You want to associate the running episode:\n\n'
     287                  ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
     288                  'with the health issue:\n\n'
     289                  ' "%(issue_name)s"\n\n'
     290                  'There already is another episode running\n'
     291                  'for this health issue:\n\n'
     292                  ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
     293                  'However, there can only be one running\n'
     294                  'episode per health issue.\n\n'
     295                  'Which episode do you want to close ?'
     296          ) % {
     297                  'new_epi_name': episode['description'],
     298                  'new_epi_start': move_range[0].strftime('%m/%y'),
     299                  'new_epi_end': move_range_end,
     300                  'issue_name': target_issue['description'],
     301                  'old_epi_name': existing_epi['description'],
     302                  'old_epi_start': exist_range[0].strftime('%m/%y'),
     303                  'old_epi_end': exist_range_end
     304          }
     305          dlg = gmGuiHelpers.c3ButtonQuestionDlg (
     306                  parent = None,
     307                  id = -1,
     308                  caption = _('Resolving two-running-episodes conflict'),
     309                  question = question,
     310                  button_defs = [
     311                          {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
     312                          {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
     313                  ]
     314          )
     315          decision = dlg.ShowModal()
     316
     317          if decision == wx.ID_CANCEL:
     318                  # button 3: move cancelled by user
     319                  return False
     320
     321          elif decision == wx.ID_YES:
     322                  # button 1: close old episode
     323                  existing_epi['episode_open'] = False
     324                  existing_epi.save_payload()
     325
     326          elif decision == wx.ID_NO:
     327                  # button 2: close new episode
     328                  episode['episode_open'] = False
     329
     330          else:
     331                  raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
     332
     333          episode['pk_health_issue'] = target_issue['pk_health_issue']
     334          if save_to_backend:
     335                  episode.save_payload()
     336          return True
     337
     338  #----------------------------------------------------------------
     339 -class cEpisodeListSelectorDlg(gmListWidgets.cGenericListSelectorDlg):
     340
     341          # FIXME: support pre-selection
     342
     343 -        def __init__(self, *args, **kwargs):
     344
     345                  episodes = kwargs['episodes']
     346                  del kwargs['episodes']
     347
     348                  gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
     349
     350                  self.SetTitle(_('Select the episodes you are interested in ...'))
     351                  self._LCTRL_items.set_columns([_('Episode'), _('Status'), _('Health Issue')])
     352                  self._LCTRL_items.set_string_items (
     353                          items = [
     354                                  [       epi['description'],
     355                                          gmTools.bool2str(epi['episode_open'], _('ongoing'), ''),
     356                                          gmTools.coalesce(epi['health_issue'], '')
     357                                  ]
     358                                  for epi in episodes ]
     359                  )
     360                  self._LCTRL_items.set_column_widths()
     361                  self._LCTRL_items.set_data(data = episodes)
     362
     363  #----------------------------------------------------------------
     364 -class cEpisodeDescriptionPhraseWheel(gmPhraseWheel.cPhraseWheel):
     365          """Let user select an episode *description*.
     366
     367          The user can select an episode description from the previously
     368          used descriptions across all episodes across all patients.
     369
     370          Selection is done with a phrasewheel so the user can
     371          type the episode name and matches will be shown. Typing
     372          "*" will show the entire list of episodes.
     373
     374          If the user types a description not existing yet a
     375          new episode description will be returned.
     376          """
     377 -        def __init__(self, *args, **kwargs):
     378
     379                  mp = gmMatchProvider.cMatchProvider_SQL2 (
     380                          queries = [
     381  """
     382  SELECT DISTINCT ON (description)
     383          description
     384                  AS data,
     385          description
     386                  AS field_label,
     387          description || ' ('
     388          || CASE
     389                  WHEN is_open IS TRUE THEN _('ongoing')
     390                  ELSE _('closed')
     391             END
     392          || ')'
     393                  AS list_label
     394  FROM
     395          clin.episode
     396  WHERE
     397          description %(fragment_condition)s
     398  ORDER BY description
     399  LIMIT 30
     400  """
     401                          ]
     402                  )
     403                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     404                  self.matcher = mp
     405
     406  #----------------------------------------------------------------
     407 -class cEpisodeSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
     408          """Let user select an episode.
     409
     410          The user can select an episode from the existing episodes of a
     411          patient. Selection is done with a phrasewheel so the user
     412          can type the episode name and matches will be shown. Typing
     413          "*" will show the entire list of episodes. Closed episodes
     414          will be marked as such. If the user types an episode name not
     415          in the list of existing episodes a new episode can be created
     416          from it if the programmer activated that feature.
     417
     418          If keyword <patient_id> is set to None or left out the control
     419          will listen to patient change signals and therefore act on
     420          gmPerson.gmCurrentPatient() changes.
     421          """
     422 -        def __init__(self, *args, **kwargs):
     423
     424                  ctxt = {'ctxt_pat': {'where_part': 'and pk_patient = %(pat)s', 'placeholder': 'pat'}}
     425
     426                  mp = gmMatchProvider.cMatchProvider_SQL2 (
     427                          queries = [
     428  """(
     429
     430  SELECT
     431          pk_episode
     432                  as data,
     433          description
     434                  as field_label,
     435          coalesce (
     436                  description || ' - ' || health_issue,
     437                  description
     438          ) as list_label,
     439          1 as rank
     440  from
     441          clin.v_pat_episodes
     442  where
     443          episode_open is true and
     444          description %(fragment_condition)s
     445          %(ctxt_pat)s
     446
     447  ) union all (
     448
     449  SELECT
     450          pk_episode
     451                  as data,
     452          description
     453                  as field_label,
     454          coalesce (
     455                  description || _(' (closed)') || ' - ' || health_issue,
     456                  description || _(' (closed)')
     457          ) as list_label,
     458          2 as rank
     459  from
     460          clin.v_pat_episodes
     461  where
     462          description %(fragment_condition)s and
     463          episode_open is false
     464          %(ctxt_pat)s
     465
     466  )
     467
     468  order by rank, list_label
     469  limit 30"""
     470  ],
     471                          context = ctxt
     472                  )
     473
     474                  try:
     475                          kwargs['patient_id']
     476                  except KeyError:
     477                          kwargs['patient_id'] = None
     478
     479                  if kwargs['patient_id'] is None:
     480                          self.use_current_patient = True
     481                          self.__register_patient_change_signals()
     482                          pat = gmPerson.gmCurrentPatient()
     483                          if pat.connected:
     484                                  mp.set_context('pat', pat.ID)
     485                  else:
     486                          self.use_current_patient = False
     487                          self.__patient_id = int(kwargs['patient_id'])
     488                          mp.set_context('pat', self.__patient_id)
     489
     490                  del kwargs['patient_id']
     491
     492                  gmPhraseWheel.cPhraseWheel.__init__ (
     493                          self,
     494                          *args,
     495                          **kwargs
     496                  )
     497                  self.matcher = mp
     498          #--------------------------------------------------------
     499          # external API
     500          #--------------------------------------------------------
     501 -        def set_patient(self, patient_id=None):
     502                  if self.use_current_patient:
     503                          return False
     504                  self.__patient_id = int(patient_id)
     505                  self.set_context('pat', self.__patient_id)
     506                  return True
     507          #--------------------------------------------------------
     508 -        def GetData(self, can_create=False, as_instance=False, is_open=False):
     509                  self.__is_open_for_create_data = is_open                # used (only) in _create_data()
     510                  return gmPhraseWheel.cPhraseWheel.GetData(self, can_create = can_create, as_instance = as_instance)
     511          #--------------------------------------------------------
     512 -        def _create_data(self):
     513
     514                  epi_name = self.GetValue().strip()
     515                  if epi_name == '':
     516                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot create episode without name.'), beep = True)
     517                          _log.debug('cannot create episode without name')
     518                          return
     519
     520                  if self.use_current_patient:
     521                          pat = gmPerson.gmCurrentPatient()
     522                  else:
     523                          pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
     524
     525                  emr = pat.emr
     526                  epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
     527                  if epi is None:
     528                          self.data = {}
     529                  else:
     530                          self.SetText (
     531                                  value = epi_name,
     532                                  data = epi['pk_episode']
     533                          )
     534          #--------------------------------------------------------
     535 -        def _data2instance(self):
     536                  return gmEMRStructItems.cEpisode(aPK_obj = self.GetData())
     537          #--------------------------------------------------------
     538          # internal API
     539          #--------------------------------------------------------
     540 -        def __register_patient_change_signals(self):
     541                  gmDispatcher.connect(self._pre_patient_unselection, 'pre_patient_unselection')
     542                  gmDispatcher.connect(self._post_patient_selection, 'post_patient_selection')
     543          #--------------------------------------------------------
     544 -        def _pre_patient_unselection(self):
     545                  self.__patient_id = None
     546                  self.unset_context('pat')
     547                  self.SetData()
     548                  return True
     549          #--------------------------------------------------------
     550 -        def _post_patient_selection(self):
     551                  if self.use_current_patient:
     552                          patient = gmPerson.gmCurrentPatient()
     553                          self.set_context('pat', patient.ID)
     554                  return True
     555
     556  #----------------------------------------------------------------
     557  from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
     558
     559 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
     560
     561 -        def __init__(self, *args, **kwargs):
     562
     563                  try:
     564                          episode = kwargs['episode']
     565                          del kwargs['episode']
     566                  except KeyError:
     567                          episode = None
     568
     569                  wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl.__init__(self, *args, **kwargs)
     570                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     571
     572                  self.data = episode
     573
     574          #----------------------------------------------------------------
     575          # generic Edit Area mixin API
     576          #----------------------------------------------------------------
     577 -        def _valid_for_save(self):
     578
     579                  errors = False
     580
     581                  if len(self._PRW_description.GetValue().strip()) == 0:
     582                          errors = True
     583                          self._PRW_description.display_as_valid(False)
     584                          self._PRW_description.SetFocus()
     585                  else:
     586                          self._PRW_description.display_as_valid(True)
     587                  self._PRW_description.Refresh()
     588
     589                  return not errors
     590
     591          #----------------------------------------------------------------
     592 -        def _save_as_new(self):
     593
     594                  pat = gmPerson.gmCurrentPatient()
     595                  emr = pat.emr
     596
     597                  epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
     598                  epi['summary'] = self._TCTRL_status.GetValue().strip()
     599                  epi['episode_open'] = not self._CHBOX_closed.IsChecked()
     600                  epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
     601
     602                  issue_name = self._PRW_issue.GetValue().strip()
     603                  if len(issue_name) != 0:
     604                          epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
     605                          issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
     606
     607                          if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
     608                                  gmDispatcher.send (
     609                                          signal = 'statustext',
     610                                          msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
     611                                                  epi['description'],
     612                                                  issue['description']
     613                                          )
     614                                  )
     615                                  gmEMRStructItems.delete_episode(episode = epi)
     616                                  return False
     617
     618                  epi.save()
     619
     620                  epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
     621
     622                  self.data = epi
     623                  return True
     624
     625          #----------------------------------------------------------------
     626 -        def _save_as_update(self):
     627
     628                  self.data['description'] = self._PRW_description.GetValue().strip()
     629                  self.data['summary'] = self._TCTRL_status.GetValue().strip()
     630                  self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
     631                  self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
     632
     633                  issue_name = self._PRW_issue.GetValue().strip()
     634                  if len(issue_name) == 0:
     635                          self.data['pk_health_issue'] = None
     636                  else:
     637                          self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
     638                          issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
     639
     640                          if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
     641                                  gmDispatcher.send (
     642                                          signal = 'statustext',
     643                                          msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
     644                                                  self.data['description'],
     645                                                  issue['description']
     646                                          )
     647                                  )
     648                                  return False
     649
     650                  self.data.save()
     651                  self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
     652
     653                  return True
     654
     655          #----------------------------------------------------------------
     656 -        def _refresh_as_new(self):
     657                  if self.data is None:
     658                          ident = gmPerson.gmCurrentPatient()
     659                  else:
     660                          ident = gmPerson.cPerson(aPK_obj = self.data['pk_patient'])
     661                  self._TCTRL_patient.SetValue(ident.get_description_gender())
     662                  self._PRW_issue.SetText()
     663                  self._PRW_description.SetText()
     664                  self._TCTRL_status.SetValue('')
     665                  self._PRW_certainty.SetText()
     666                  self._CHBOX_closed.SetValue(False)
     667                  self._PRW_codes.SetText()
     668
     669                  self._PRW_issue.SetFocus()
     670
     671          #----------------------------------------------------------------
     672 -        def _refresh_from_existing(self):
     673                  ident = gmPerson.cPerson(aPK_obj = self.data['pk_patient'])
     674                  self._TCTRL_patient.SetValue(ident.get_description_gender())
     675
     676                  if self.data['pk_health_issue'] is not None:
     677                          self._PRW_issue.SetText (
     678                                  self.data['health_issue'],
     679                                  data = self.data['pk_health_issue']
     680                          )
     681
     682                  self._PRW_description.SetText (
     683                          self.data['description'],
     684                          data = self.data['description']
     685                  )
     686
     687                  self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], ''))
     688
     689                  if self.data['diagnostic_certainty_classification'] is not None:
     690                          self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
     691
     692                  self._CHBOX_closed.SetValue(not self.data['episode_open'])
     693
     694                  val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
     695                  self._PRW_codes.SetText(val, data)
     696
     697                  if self.data['pk_health_issue'] is None:
     698                          self._PRW_issue.SetFocus()
     699                  else:
     700                          self._PRW_description.SetFocus()
     701
     702          #----------------------------------------------------------------
     703 -        def _refresh_as_new_from_existing(self):
     704                  self._refresh_as_new()
     705
     706  #================================================================
     707  # health issue related widgets/functions
     708  #----------------------------------------------------------------
     709 -def edit_health_issue(parent=None, issue=None):
     710          ea = cHealthIssueEditAreaPnl(parent, -1)
     711          ea.data = issue
     712          ea.mode = gmTools.coalesce(issue, 'new', 'edit')
     713          dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = (issue is not None))
     714          dlg.SetTitle(gmTools.coalesce(issue, _('Adding a new health issue'), _('Editing a health issue')))
     715          if dlg.ShowModal() == wx.ID_OK:
     716                  dlg.Destroy()
     717                  return True
     718          dlg.Destroy()
     719          return False
     720  #----------------------------------------------------------------
     721 -def select_health_issues(parent=None, emr=None):
     722
     723          if parent is None:
     724                  parent = wx.GetApp().GetTopWindow()
     725          #-----------------------------------------
     726          def edit(issue=None):
     727                  return edit_health_issue(parent = parent, issue = issue)
     728          #-----------------------------------------
     729          def delete(issue=None):
     730                  if gmEMRStructItems.delete_health_issue(health_issue = issue):
     731                          return True
     732                  gmDispatcher.send (
     733                          signal = 'statustext',
     734                          msg = _('Cannot delete health issue.'),
     735                          beep = True
     736                  )
     737                  return False
     738          #-----------------------------------------
     739          def get_tooltip(data):
     740                  if data is None:
     741                          return None
     742                  patient = gmPerson.cPatient(data['pk_patient'])
     743                  return data.format (
     744                          patient = patient,
     745                          with_summary = True,
     746                          with_codes = True,
     747                          with_episodes = True,
     748                          with_encounters = True,
     749                          with_medications = False,
     750                          with_hospital_stays = False,
     751                          with_procedures = False,
     752                          with_family_history = False,
     753                          with_documents = False,
     754                          with_tests = False,
     755                          with_vaccinations = False
     756                  )
     757          #-----------------------------------------
     758          def refresh(lctrl):
     759                  issues = emr.get_health_issues()
     760                  items = [
     761                          [
     762                                  gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), '', ''),
     763                                  i['description'],
     764                                  gmTools.bool2subst(i['clinically_relevant'], _('relevant'), '', ''),
     765                                  gmTools.bool2subst(i['is_active'], _('active'), '', ''),
     766                                  gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), '', '')
     767                          ] for i in issues
     768                  ]
     769                  lctrl.set_string_items(items = items)
     770                  lctrl.set_data(data = issues)
     771          #-----------------------------------------
     772          return gmListWidgets.get_choices_from_list (
     773                  parent = parent,
     774                  msg = _('\nSelect the health issues !\n'),
     775                  caption = _('Showing health issues ...'),
     776                  columns = ['', _('Health issue'), '', '', ''],
     777                  single_selection = False,
     778                  edit_callback = edit,
     779                  new_callback = edit,
     780                  delete_callback = delete,
     781                  refresh_callback = refresh
     782          )
     783  #----------------------------------------------------------------
     784 -class cIssueListSelectorDlg(gmListWidgets.cGenericListSelectorDlg):
     785
     786          # FIXME: support pre-selection
     787
     788 -        def __init__(self, *args, **kwargs):
     789
     790                  issues = kwargs['issues']
     791                  del kwargs['issues']
     792
     793                  gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
     794
     795                  self.SetTitle(_('Select the health issues you are interested in ...'))
     796                  self._LCTRL_items.set_columns(['', _('Health Issue'), '', '', ''])
     797
     798                  for issue in issues:
     799                          if issue['is_confidential']:
     800                                  row_num = self._LCTRL_items.InsertItem(sys.maxsize, label = _('confidential'))
     801                                  self._LCTRL_items.SetItemTextColour(row_num, col=wx.Colour('RED'))
     802                          else:
     803                                  row_num = self._LCTRL_items.InsertItem(sys.maxsize, label = '')
     804
     805                          self._LCTRL_items.SetItem(index = row_num, column = 1, label = issue['description'])
     806                          if issue['clinically_relevant']:
     807                                  self._LCTRL_items.SetItem(index = row_num, column = 2, label = _('relevant'))
     808                          if issue['is_active']:
     809                                  self._LCTRL_items.SetItem(index = row_num, column = 3, label = _('active'))
     810                          if issue['is_cause_of_death']:
     811                                  self._LCTRL_items.SetItem(index = row_num, column = 4, label = _('fatal'))
     812
     813                  self._LCTRL_items.set_column_widths()
     814                  self._LCTRL_items.set_data(data = issues)
     815
     816  #----------------------------------------------------------------
     817 -class cIssueSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
     818          """Let the user select a health issue.
     819
     820          The user can select a health issue from the existing issues
     821          of a patient. Selection is done with a phrasewheel so the user
     822          can type the issue name and matches will be shown. Typing
     823          "*" will show the entire list of issues. Inactive issues
     824          will be marked as such. If the user types an issue name not
     825          in the list of existing issues a new issue can be created
     826          from it if the programmer activated that feature.
     827
     828          If keyword <patient_id> is set to None or left out the control
     829          will listen to patient change signals and therefore act on
     830          gmPerson.gmCurrentPatient() changes.
     831          """
     832 -        def __init__(self, *args, **kwargs):
     833
     834                  ctxt = {'ctxt_pat': {'where_part': 'pk_patient=%(pat)s', 'placeholder': 'pat'}}
     835
     836                  mp = gmMatchProvider.cMatchProvider_SQL2 (
     837                          # FIXME: consider clin.health_issue.clinically_relevant
     838                          queries = ["""
     839                                  SELECT
     840                                          data,
     841                                          field_label,
     842                                          list_label
     843                                  FROM ((
     844                                          SELECT
     845                                                  pk_health_issue AS data,
     846                                                  description AS field_label,
     847                                                  description AS list_label
     848                                          FROM clin.v_health_issues
     849                                          WHERE
     850                                                  is_active IS true
     851                                                          AND
     852                                                  description %(fragment_condition)s
     853                                                          AND
     854                                                  %(ctxt_pat)s
     855
     856                                          ) UNION (
     857
     858                                          SELECT
     859                                                  pk_health_issue AS data,
     860                                                  description AS field_label,
     861                                                  description || _(' (inactive)') AS list_label
     862                                          FROM clin.v_health_issues
     863                                          WHERE
     864                                                  is_active IS false
     865                                                          AND
     866                                                  description %(fragment_condition)s
     867                                                          AND
     868                                                  %(ctxt_pat)s
     869                                  )) AS union_query
     870                                  ORDER BY
     871                                          list_label"""
     872                          ],
     873                          context = ctxt
     874                  )
     875                  try: kwargs['patient_id']
     876                  except KeyError: kwargs['patient_id'] = None
     877
     878                  if kwargs['patient_id'] is None:
     879                          self.use_current_patient = True
     880                          self.__register_patient_change_signals()
     881                          pat = gmPerson.gmCurrentPatient()
     882                          if pat.connected:
     883                                  mp.set_context('pat', pat.ID)
     884                  else:
     885                          self.use_current_patient = False
     886                          self.__patient_id = int(kwargs['patient_id'])
     887                          mp.set_context('pat', self.__patient_id)
     888
     889                  del kwargs['patient_id']
     890
     891                  gmPhraseWheel.cPhraseWheel.__init__ (
     892                          self,
     893                          *args,
     894                          **kwargs
     895                  )
     896                  self.matcher = mp
     897          #--------------------------------------------------------
     898          # external API
     899          #--------------------------------------------------------
     900 -        def set_patient(self, patient_id=None):
     901                  if self.use_current_patient:
     902                          return False
     903                  self.__patient_id = int(patient_id)
     904                  self.set_context('pat', self.__patient_id)
     905                  return True
     906          #--------------------------------------------------------
     907 -        def _create_data(self):
     908                  issue_name = self.GetValue().strip()
     909                  if issue_name == '':
     910                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot create health issue without name.'), beep = True)
     911                          _log.debug('cannot create health issue without name')
     912                          return
     913
     914                  if self.use_current_patient:
     915                          pat = gmPerson.gmCurrentPatient()
     916                  else:
     917                          pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
     918
     919                  emr = pat.emr
     920                  issue = emr.add_health_issue(issue_name = issue_name)
     921
     922                  if issue is None:
     923                          self.data = {}
     924                  else:
     925                          self.SetText (
     926                                  value = issue_name,
     927                                  data = issue['pk_health_issue']
     928                          )
     929          #--------------------------------------------------------
     930 -        def _data2instance(self):
     931                  return gmEMRStructItems.cHealthIssue(aPK_obj = self.GetData())
     932          #--------------------------------------------------------
     933 -        def _get_data_tooltip(self):
     934                  if self.GetData() is None:
     935                          return None
     936                  issue = self._data2instance()
     937                  if issue is None:
     938                          return None
     939                  return issue.format (
     940                          patient = None,
     941                          with_summary = True,
     942                          with_codes = False,
     943                          with_episodes = True,
     944                          with_encounters = True,
     945                          with_medications = False,
     946                          with_hospital_stays = False,
     947                          with_procedures = False,
     948                          with_family_history = False,
     949                          with_documents = False,
     950                          with_tests = False,
     951                          with_vaccinations = False,
     952                          with_external_care = True
     953                  )
     954          #--------------------------------------------------------
     955          # internal API
     956          #--------------------------------------------------------
     957 -        def __register_patient_change_signals(self):
     958                  gmDispatcher.connect(self._pre_patient_unselection, 'pre_patient_unselection')
     959                  gmDispatcher.connect(self._post_patient_selection, 'post_patient_selection')
     960          #--------------------------------------------------------
     961 -        def _pre_patient_unselection(self):
     962                  return True
     963          #--------------------------------------------------------
     964 -        def _post_patient_selection(self):
     965                  if self.use_current_patient:
     966                          patient = gmPerson.gmCurrentPatient()
     967                          self.set_context('pat', patient.ID)
     968                  return True
     969  #------------------------------------------------------------
     970  from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
     971
     972 -class cIssueSelectionDlg(wxgIssueSelectionDlg.wxgIssueSelectionDlg):
     973
     974 -        def __init__(self, *args, **kwargs):
     975                  try:
     976                          msg = kwargs['message']
     977                  except KeyError:
     978                          msg = None
     979                  del kwargs['message']
     980                  wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
     981                  if msg is not None:
     982                          self._lbl_message.SetLabel(label=msg)
     983          #--------------------------------------------------------
     984 -        def _on_OK_button_pressed(self, event):
     985                  event.Skip()
     986                  pk_issue = self._PhWheel_issue.GetData(can_create=True)
     987                  if pk_issue is None:
     988                          gmGuiHelpers.gm_show_error (
     989                                  _('Cannot create new health issue:\n [%(issue)s]') % {'issue': self._PhWheel_issue.GetValue().strip()},
     990                                  _('Selecting health issue')
     991                          )
     992                          return False
     993                  return True
     994  #------------------------------------------------------------
     995  from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
     996
     997 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
     998          """Panel encapsulating health issue edit area functionality."""
     999
    1000 -        def __init__(self, *args, **kwargs):
    1001
    1002                  try:
    1003                          data = kwargs['issue']
    1004                          del kwargs['issue']
    1005                  except KeyError:
    1006                          data = None
    1007
    1008                  wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
    1009                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    1010
    1011                  self.mode = 'new'
    1012                  self.data = data
    1013                  if data is not None:
    1014                          self.mode = 'edit'
    1015
    1016                  self.__init_ui()
    1017          #----------------------------------------------------------------
    1018 -        def __init_ui(self):
    1019
    1020                  # FIXME: include more sources: coding systems/other database columns
    1021                  mp = gmMatchProvider.cMatchProvider_SQL2 (
    1022                          queries = ["SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
    1023                  )
    1024                  mp.setThresholds(1, 3, 5)
    1025                  self._PRW_condition.matcher = mp
    1026
    1027                  mp = gmMatchProvider.cMatchProvider_SQL2 (
    1028                          queries = ["""
    1029  SELECT DISTINCT ON (grouping) grouping, grouping from (
    1030
    1031          SELECT rank, grouping from ((
    1032
    1033                  SELECT
    1034                          grouping,
    1035                          1 as rank
    1036                  from
    1037                          clin.health_issue
    1038                  where
    1039                          grouping %%(fragment_condition)s
    1040                                  and
    1041                          (SELECT True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
    1042
    1043          ) union (
    1044
    1045                  SELECT
    1046                          grouping,
    1047                          2 as rank
    1048                  from
    1049                          clin.health_issue
    1050                  where
    1051                          grouping %%(fragment_condition)s
    1052
    1053          )) as union_result
    1054
    1055          order by rank
    1056
    1057  ) as order_result
    1058
    1059  limit 50""" % gmPerson.gmCurrentPatient().ID
    1060                          ]
    1061                  )
    1062                  mp.setThresholds(1, 3, 5)
    1063                  self._PRW_grouping.matcher = mp
    1064
    1065                  self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
    1066                  self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
    1067
    1068  #               self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
    1069  #               self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
    1070
    1071                  self._PRW_year_noted.Enable(True)
    1072
    1073                  self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
    1074
    1075          #----------------------------------------------------------------
    1076          # generic Edit Area mixin API
    1077          #----------------------------------------------------------------
    1078 -        def _valid_for_save(self):
    1079
    1080                  if self._PRW_condition.GetValue().strip() == '':
    1081                          self._PRW_condition.display_as_valid(False)
    1082                          self._PRW_condition.SetFocus()
    1083                          return False
    1084                  self._PRW_condition.display_as_valid(True)
    1085                  self._PRW_condition.Refresh()
    1086
    1087                  # FIXME: sanity check age/year diagnosed
    1088                  age_noted = self._PRW_age_noted.GetValue().strip()
    1089                  if age_noted != '':
    1090                          if gmDateTime.str2interval(str_interval = age_noted) is None:
    1091                                  self._PRW_age_noted.display_as_valid(False)
    1092                                  self._PRW_age_noted.SetFocus()
    1093                                  return False
    1094                  self._PRW_age_noted.display_as_valid(True)
    1095                  return True
    1096          #----------------------------------------------------------------
    1097 -        def _save_as_new(self):
    1098                  pat = gmPerson.gmCurrentPatient()
    1099                  emr = pat.emr
    1100
    1101                  issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
    1102
    1103                  side = ''
    1104                  if self._ChBOX_left.GetValue():
    1105                          side += 's'
    1106                  if self._ChBOX_right.GetValue():
    1107                          side += 'd'
    1108                  issue['laterality'] = side
    1109
    1110                  issue['summary'] = self._TCTRL_status.GetValue().strip()
    1111                  issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
    1112                  issue['grouping'] = self._PRW_grouping.GetValue().strip()
    1113                  issue['is_active'] = self._ChBOX_active.GetValue()
    1114                  issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
    1115                  issue['is_confidential'] = self._ChBOX_confidential.GetValue()
    1116                  issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
    1117
    1118                  age_noted = self._PRW_age_noted.GetData()
    1119                  if age_noted is not None:
    1120                          issue['age_noted'] = age_noted
    1121
    1122                  issue.save()
    1123
    1124                  issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
    1125
    1126                  self.data = issue
    1127                  return True
    1128          #----------------------------------------------------------------
    1129 -        def _save_as_update(self):
    1130
    1131                  self.data['description'] = self._PRW_condition.GetValue().strip()
    1132
    1133                  side = ''
    1134                  if self._ChBOX_left.GetValue():
    1135                          side += 's'
    1136                  if self._ChBOX_right.GetValue():
    1137                          side += 'd'
    1138                  self.data['laterality'] = side
    1139
    1140                  self.data['summary'] = self._TCTRL_status.GetValue().strip()
    1141                  self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
    1142                  self.data['grouping'] = self._PRW_grouping.GetValue().strip()
    1143                  self.data['is_active'] = bool(self._ChBOX_active.GetValue())
    1144                  self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
    1145                  self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
    1146                  self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
    1147
    1148                  age_noted = self._PRW_age_noted.GetData()
    1149                  if age_noted is not None:
    1150                          self.data['age_noted'] = age_noted
    1151
    1152                  self.data.save()
    1153                  self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
    1154
    1155                  return True
    1156          #----------------------------------------------------------------
    1157 -        def _refresh_as_new(self):
    1158                  self._PRW_condition.SetText()
    1159                  self._ChBOX_left.SetValue(0)
    1160                  self._ChBOX_right.SetValue(0)
    1161                  self._PRW_codes.SetText()
    1162                  self._on_leave_codes()
    1163                  self._PRW_certainty.SetText()
    1164                  self._PRW_grouping.SetText()
    1165                  self._TCTRL_status.SetValue('')
    1166                  self._PRW_age_noted.SetText()
    1167                  self._PRW_year_noted.SetText()
    1168                  self._ChBOX_active.SetValue(1)
    1169                  self._ChBOX_relevant.SetValue(1)
    1170                  self._ChBOX_confidential.SetValue(0)
    1171                  self._ChBOX_caused_death.SetValue(0)
    1172
    1173                  self._PRW_condition.SetFocus()
    1174                  return True
    1175          #----------------------------------------------------------------
    1176 -        def _refresh_from_existing(self):
    1177                  self._PRW_condition.SetText(self.data['description'])
    1178
    1179                  lat = gmTools.coalesce(self.data['laterality'], '')
    1180                  if lat.find('s') == -1:
    1181                          self._ChBOX_left.SetValue(0)
    1182                  else:
    1183                          self._ChBOX_left.SetValue(1)
    1184                  if lat.find('d') == -1:
    1185                          self._ChBOX_right.SetValue(0)
    1186                  else:
    1187                          self._ChBOX_right.SetValue(1)
    1188
    1189                  val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
    1190                  self._PRW_codes.SetText(val, data)
    1191                  self._on_leave_codes()
    1192
    1193                  if self.data['diagnostic_certainty_classification'] is not None:
    1194                          self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
    1195                  self._PRW_grouping.SetText(gmTools.coalesce(self.data['grouping'], ''))
    1196                  self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], ''))
    1197
    1198                  if self.data['age_noted'] is None:
    1199                          self._PRW_age_noted.SetText()
    1200                  else:
    1201                          self._PRW_age_noted.SetText (
    1202                                  value = '%sd' % self.data['age_noted'].days,
    1203                                  data = self.data['age_noted']
    1204                          )
    1205
    1206                  self._ChBOX_active.SetValue(self.data['is_active'])
    1207                  self._ChBOX_relevant.SetValue(self.data['clinically_relevant'])
    1208                  self._ChBOX_confidential.SetValue(self.data['is_confidential'])
    1209                  self._ChBOX_caused_death.SetValue(self.data['is_cause_of_death'])
    1210
    1211                  self._TCTRL_status.SetFocus()
    1212
    1213                  return True
    1214          #----------------------------------------------------------------
    1215 -        def _refresh_as_new_from_existing(self):
    1216                  return self._refresh_as_new()
    1217          #--------------------------------------------------------
    1218          # internal helpers
    1219          #--------------------------------------------------------
    1220 -        def _on_leave_codes(self, *args, **kwargs):
    1221                  if not self._PRW_codes.IsModified():
    1222                          return True
    1223
    1224                  self._TCTRL_code_details.SetValue('- ' + '\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
    1225          #--------------------------------------------------------
    1226 -        def _on_leave_age_noted(self, *args, **kwargs):
    1227
    1228                  if not self._PRW_age_noted.IsModified():
    1229                          return True
    1230
    1231                  age_str = self._PRW_age_noted.GetValue().strip()
    1232
    1233                  if age_str == '':
    1234                          return True
    1235
    1236                  issue_age = gmDateTime.str2interval(str_interval = age_str)
    1237
    1238                  if issue_age is None:
    1239                          self.status_message = _('Cannot parse [%s] into valid interval.') % age_str
    1240                          self._PRW_age_noted.display_as_valid(False)
    1241                          return True
    1242
    1243                  pat = gmPerson.gmCurrentPatient()
    1244                  if pat['dob'] is not None:
    1245                          max_issue_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
    1246                          if issue_age >= max_issue_age:
    1247                                  self.status_message = _('Health issue cannot have been noted at age %s. Patient is only %s old.') % (issue_age, pat.get_medical_age())
    1248                                  self._PRW_age_noted.display_as_valid(False)
    1249                                  return True
    1250
    1251                  self._PRW_age_noted.display_as_valid(True)
    1252                  self._PRW_age_noted.SetText(value = age_str, data = issue_age)
    1253
    1254                  if pat['dob'] is not None:
    1255                          fts = gmDateTime.cFuzzyTimestamp (
    1256                                  timestamp = pat['dob'] + issue_age,
    1257                                  accuracy = gmDateTime.acc_months
    1258                          )
    1259                          self._PRW_year_noted.SetText(value = str(fts), data = fts)
    1260
    1261                  return True
    1262          #--------------------------------------------------------
    1263 -        def _on_leave_year_noted(self, *args, **kwargs):
    1264
    1265                  if not self._PRW_year_noted.IsModified():
    1266                          return True
    1267
    1268                  year_noted = self._PRW_year_noted.GetData()
    1269
    1270                  if year_noted is None:
    1271                          if self._PRW_year_noted.GetValue().strip() == '':
    1272                                  self._PRW_year_noted.display_as_valid(True)
    1273                                  return True
    1274                          self._PRW_year_noted.display_as_valid(False)
    1275                          return True
    1276
    1277                  year_noted = year_noted.get_pydt()
    1278
    1279                  if year_noted >= pydt.datetime.now(tz = year_noted.tzinfo):
    1280                          self.status_message = _('Condition diagnosed in the future.')
    1281                          self._PRW_year_noted.display_as_valid(False)
    1282                          return True
    1283
    1284                  self._PRW_year_noted.display_as_valid(True)
    1285
    1286                  pat = gmPerson.gmCurrentPatient()
    1287                  if pat['dob'] is not None:
    1288                          issue_age = year_noted - pat['dob']
    1289                          age_str = gmDateTime.format_interval_medically(interval = issue_age)
    1290                          self._PRW_age_noted.SetText(age_str, issue_age, True)
    1291
    1292                  return True
    1293          #--------------------------------------------------------
    1294 -        def _on_modified_age_noted(self, *args, **kwargs):
    1295                  wx.CallAfter(self._PRW_year_noted.SetText, '', None, True)
    1296                  return True
    1297          #--------------------------------------------------------
    1298 -        def _on_modified_year_noted(self, *args, **kwargs):
    1299                  wx.CallAfter(self._PRW_age_noted.SetText, '', None, True)
    1300                  return True
    1301  #================================================================
    1302  # diagnostic certainty related widgets/functions
    1303  #----------------------------------------------------------------
    1304 -class cDiagnosticCertaintyClassificationPhraseWheel(gmPhraseWheel.cPhraseWheel):
    1305
    1306 -        def __init__(self, *args, **kwargs):
    1307
    1308                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    1309
    1310                  self.selection_only = False                     # can be NULL, too
    1311
    1312                  mp = gmMatchProvider.cMatchProvider_FixedList (
    1313                          aSeq = [
    1314                                  {'data': 'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('A'), 'weight': 1},
    1315                                  {'data': 'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('B'), 'weight': 1},
    1316                                  {'data': 'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('C'), 'weight': 1},
    1317                                  {'data': 'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('D'), 'weight': 1}
    1318                          ]
    1319                  )
    1320                  mp.setThresholds(1, 2, 4)
    1321                  self.matcher = mp
    1322
    1323                  self.SetToolTip(_(
    1324                          "The diagnostic classification or grading of this assessment.\n"
    1325                          "\n"
    1326                          "This documents how certain one is about this being a true diagnosis."
    1327                  ))
    1328
    1329  #================================================================
    1330  # MAIN
    1331  #----------------------------------------------------------------
    1332  if __name__ == '__main__':
    1333
    1334          if len(sys.argv) < 2:
    1335                  sys.exit()
    1336
    1337          if sys.argv[1] != 'test':
    1338                  sys.exit()
    1339
    1340          from Gnumed.business import gmPersonSearch
    1341          from Gnumed.wxpython import gmPatSearchWidgets
    1342
    1343          #================================================================
    1344 -        class testapp (wx.App):
    1345                          """
    1346                          Test application for testing EMR struct widgets
    1347                          """
    1348                          #--------------------------------------------------------
    1349 -                        def OnInit (self):
    1350                                  """
    1351                                  Create test application UI
    1352                                  """
    1353                                  frame = wx.Frame (
    1354                                                          None,
    1355                                                          -4,
    1356                                                          'Testing EMR struct widgets',
    1357                                                          size=wx.Size(600, 400),
    1358                                                          style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
    1359                                                  )
    1360                                  filemenu = wx.Menu()
    1361                                  filemenu.AppendSeparator()
    1362                                  item = filemenu.Append(ID_EXIT, "E&xit"," Terminate test application")
    1363                                  self.Bind(wx.EVT_MENU, self.OnCloseWindow, item)
    1364
    1365                                  # Creating the menubar.
    1366                                  menuBar = wx.MenuBar()
    1367                                  menuBar.Append(filemenu,"&File")
    1368
    1369                                  frame.SetMenuBar(menuBar)
    1370
    1371                                  txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
    1372                                  wx.DefaultPosition, wx.DefaultSize, 0 )
    1373
    1374                                  # patient EMR
    1375                                  self.__pat = gmPerson.gmCurrentPatient()
    1376
    1377                                  frame.Show(1)
    1378                                  return 1
    1379                          #--------------------------------------------------------
    1380 -                        def OnCloseWindow (self, e):
    1381                                  """
    1382                                  Close test aplication
    1383                                  """
    1384                                  self.ExitMainLoop ()
    1385
    1386          #----------------------------------------------------------------
    1387 -        def test_epsiode_edit_area_pnl():
    1388                  app = wx.PyWidgetTester(size = (200, 300))
    1389                  emr = pat.emr
    1390                  epi = emr.get_episodes()[0]
    1391                  pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi)
    1392                  app.frame.Show(True)
    1393                  app.MainLoop()
    1394          #----------------------------------------------------------------
    1395 -        def test_episode_edit_area_dialog():
    1396                  app = wx.PyWidgetTester(size = (200, 300))
    1397                  emr = pat.emr
    1398                  epi = emr.get_episodes()[0]
    1399                  edit_episode(parent=app.frame, episode=epi)
    1400
    1401          #----------------------------------------------------------------
    1402 -        def test_episode_selection_prw():
    1403                  app = wx.PyWidgetTester(size = (400, 40))
    1404                  app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
    1405  #               app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(350,20), pos=(10,20), patient_id=pat.ID)
    1406                  app.MainLoop()
    1407
    1408          #----------------------------------------------------------------
    1409 -        def test_health_issue_edit_area_dlg():
    1410                  app = wx.PyWidgetTester(size = (200, 300))
    1411                  edit_health_issue(parent=app.frame, issue=None)
    1412
    1413          #----------------------------------------------------------------
    1414 -        def test_health_issue_edit_area_pnl():
    1415                  app = wx.PyWidgetTester(size = (200, 300))
    1416                  app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
    1417                  app.MainLoop()
    1418
    1419          #================================================================
    1420
    1421          # obtain patient
    1422          pat = gmPersonSearch.ask_for_patient()
    1423          if pat is None:
    1424                  print("No patient. Exiting gracefully...")
    1425                  sys.exit(0)
    1426          gmPatSearchWidgets.set_active_patient(patient=pat)
    1427
    1428  #       try:
    1429                  # lauch emr dialogs test application
    1430  #               app = testapp(0)
    1431  #               app.MainLoop()
    1432  #       except Exception:
    1433  #               _log.exception("unhandled exception caught !")
    1434                  # but re-raise them
    1435  #               raise
    1436
    1437          #test_epsiode_edit_area_pnl()
    1438          #test_episode_edit_area_dialog()
    1439          #test_health_issue_edit_area_dlg()
    1440          test_episode_selection_prw()
    1441