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, single_entry=True):
      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 = single_entry)
      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.DestroyLater()
     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,
     198                                          _('Enter a short descriptive name for the new health issue:'),
     199                                          caption = _('Creating a new health issue ...'),
     200                                          value = issue_name,
     201                                          style = wx.OK | wx.CANCEL | wx.CENTRE
     202                                  )
     203                                  decision = dlg.ShowModal()
     204                                  if decision != wx.ID_OK:
     205                                          dlg.DestroyLater()
     206                                          return
     207                                  issue_name = dlg.GetValue().strip()
     208                                  dlg.DestroyLater()
     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                  try:
     424                          self.__patient_id = int(kwargs['patient_id'])
     425                          self.use_current_patient = False
     426                          del kwargs['patient_id']
     427                  except KeyError:
     428                          self.__patient_id = None
     429                          self.use_current_patient = True
     430
     431                  mp = gmEMRStructItems.cEpisodeMatchProvider()
     432                  if self.use_current_patient:
     433                          self.__register_patient_change_signals()
     434                          pat = gmPerson.gmCurrentPatient()
     435                          if pat.connected:
     436                                  mp.set_context('pat', pat.ID)
     437                  else:
     438                          mp.set_context('pat', self.__patient_id)
     439
     440                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     441
     442                  self.matcher = mp
     443
     444          #--------------------------------------------------------
     445          # external API
     446          #--------------------------------------------------------
     447 -        def set_patient(self, patient_id=None):
     448                  if self.use_current_patient:
     449                          return False
     450                  self.__patient_id = int(patient_id)
     451                  self.set_context('pat', self.__patient_id)
     452                  return True
     453
     454          #--------------------------------------------------------
     455 -        def GetData(self, can_create=False, as_instance=False, is_open=False):
     456                  self.__is_open_for_create_data = is_open                # used (only) in _create_data()
     457                  return gmPhraseWheel.cPhraseWheel.GetData(self, can_create = can_create, as_instance = as_instance)
     458
     459          #--------------------------------------------------------
     460 -        def _create_data(self):
     461
     462                  epi_name = self.GetValue().strip()
     463                  if epi_name == '':
     464                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot create episode without name.'), beep = True)
     465                          _log.debug('cannot create episode without name')
     466                          return
     467
     468                  if self.use_current_patient:
     469                          pat = gmPerson.gmCurrentPatient()
     470                  else:
     471                          pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
     472
     473                  emr = pat.emr
     474                  epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
     475                  if epi is None:
     476                          self.data = {}
     477                  else:
     478                          self.SetText (
     479                                  value = epi_name,
     480                                  data = epi['pk_episode']
     481                          )
     482
     483          #--------------------------------------------------------
     484 -        def _data2instance(self):
     485                  return gmEMRStructItems.cEpisode(aPK_obj = self.GetData())
     486
     487          #--------------------------------------------------------
     488          # internal API
     489          #--------------------------------------------------------
     490 -        def __register_patient_change_signals(self):
     491                  gmDispatcher.connect(self._pre_patient_unselection, 'pre_patient_unselection')
     492                  gmDispatcher.connect(self._post_patient_selection, 'post_patient_selection')
     493
     494          #--------------------------------------------------------
     495 -        def _pre_patient_unselection(self):
     496                  self.__patient_id = None
     497                  self.unset_context('pat')
     498                  self.SetData()
     499                  return True
     500
     501          #--------------------------------------------------------
     502 -        def _post_patient_selection(self):
     503                  if self.use_current_patient:
     504                          patient = gmPerson.gmCurrentPatient()
     505                          self.set_context('pat', patient.ID)
     506                  return True
     507
     508  #----------------------------------------------------------------
     509  from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
     510
     511 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
     512
     513 -        def __init__(self, *args, **kwargs):
     514
     515                  try:
     516                          episode = kwargs['episode']
     517                          del kwargs['episode']
     518                  except KeyError:
     519                          episode = None
     520
     521                  wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl.__init__(self, *args, **kwargs)
     522                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     523
     524                  self.data = episode
     525
     526          #----------------------------------------------------------------
     527          # generic Edit Area mixin API
     528          #----------------------------------------------------------------
     529 -        def _valid_for_save(self):
     530
     531                  errors = False
     532
     533                  if len(self._PRW_description.GetValue().strip()) == 0:
     534                          errors = True
     535                          self._PRW_description.display_as_valid(False)
     536                          self._PRW_description.SetFocus()
     537                  else:
     538                          self._PRW_description.display_as_valid(True)
     539                  self._PRW_description.Refresh()
     540
     541                  return not errors
     542
     543          #----------------------------------------------------------------
     544 -        def _save_as_new(self):
     545
     546                  pat = gmPerson.gmCurrentPatient()
     547                  emr = pat.emr
     548
     549                  epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
     550                  epi['summary'] = self._TCTRL_status.GetValue().strip()
     551                  epi['episode_open'] = not self._CHBOX_closed.IsChecked()
     552                  epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
     553
     554                  issue_name = self._PRW_issue.GetValue().strip()
     555                  if len(issue_name) != 0:
     556                          epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
     557                          issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
     558
     559                          if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
     560                                  gmDispatcher.send (
     561                                          signal = 'statustext',
     562                                          msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
     563                                                  epi['description'],
     564                                                  issue['description']
     565                                          )
     566                                  )
     567                                  gmEMRStructItems.delete_episode(episode = epi)
     568                                  return False
     569
     570                  epi.save()
     571
     572                  epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
     573
     574                  self.data = epi
     575                  return True
     576
     577          #----------------------------------------------------------------
     578 -        def _save_as_update(self):
     579
     580                  self.data['description'] = self._PRW_description.GetValue().strip()
     581                  self.data['summary'] = self._TCTRL_status.GetValue().strip()
     582                  self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
     583                  self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
     584
     585                  issue_name = self._PRW_issue.GetValue().strip()
     586                  if len(issue_name) == 0:
     587                          self.data['pk_health_issue'] = None
     588                  else:
     589                          self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
     590                          issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
     591
     592                          if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
     593                                  gmDispatcher.send (
     594                                          signal = 'statustext',
     595                                          msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
     596                                                  self.data['description'],
     597                                                  issue['description']
     598                                          )
     599                                  )
     600                                  return False
     601
     602                  self.data.save()
     603                  self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
     604
     605                  return True
     606
     607          #----------------------------------------------------------------
     608 -        def _refresh_as_new(self):
     609                  if self.data is None:
     610                          ident = gmPerson.gmCurrentPatient()
     611                  else:
     612                          ident = gmPerson.cPerson(aPK_obj = self.data['pk_patient'])
     613                  self._TCTRL_patient.SetValue(ident.get_description_gender())
     614                  self._PRW_issue.SetText()
     615                  self._PRW_description.SetText()
     616                  self._TCTRL_status.SetValue('')
     617                  self._PRW_certainty.SetText()
     618                  self._CHBOX_closed.SetValue(False)
     619                  self._PRW_codes.SetText()
     620
     621                  self._PRW_issue.SetFocus()
     622
     623          #----------------------------------------------------------------
     624 -        def _refresh_from_existing(self):
     625                  ident = gmPerson.cPerson(aPK_obj = self.data['pk_patient'])
     626                  self._TCTRL_patient.SetValue(ident.get_description_gender())
     627
     628                  if self.data['pk_health_issue'] is not None:
     629                          self._PRW_issue.SetText (
     630                                  self.data['health_issue'],
     631                                  data = self.data['pk_health_issue']
     632                          )
     633
     634                  self._PRW_description.SetText (
     635                          self.data['description'],
     636                          data = self.data['description']
     637                  )
     638
     639                  self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], ''))
     640
     641                  if self.data['diagnostic_certainty_classification'] is not None:
     642                          self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
     643
     644                  self._CHBOX_closed.SetValue(not self.data['episode_open'])
     645
     646                  val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
     647                  self._PRW_codes.SetText(val, data)
     648
     649                  if self.data['pk_health_issue'] is None:
     650                          self._PRW_issue.SetFocus()
     651                  else:
     652                          self._PRW_description.SetFocus()
     653
     654          #----------------------------------------------------------------
     655 -        def _refresh_as_new_from_existing(self):
     656                  self._refresh_as_new()
     657
     658  #================================================================
     659  # health issue related widgets/functions
     660  #----------------------------------------------------------------
     661 -def edit_health_issue(parent=None, issue=None, single_entry=False):
     662          ea = cHealthIssueEditAreaPnl(parent, -1)
     663          ea.data = issue
     664          ea.mode = gmTools.coalesce(issue, 'new', 'edit')
     665          dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
     666          dlg.SetTitle(gmTools.coalesce(issue, _('Adding a new health issue'), _('Editing a health issue')))
     667          if dlg.ShowModal() == wx.ID_OK:
     668                  dlg.DestroyLater()
     669                  return True
     670          dlg.DestroyLater()
     671          return False
     672
     673  #----------------------------------------------------------------
     674 -def select_health_issues(parent=None, emr=None):
     675
     676          if parent is None:
     677                  parent = wx.GetApp().GetTopWindow()
     678          #-----------------------------------------
     679          def edit(issue=None):
     680                  return edit_health_issue(parent = parent, issue = issue)
     681          #-----------------------------------------
     682          def delete(issue=None):
     683                  if gmEMRStructItems.delete_health_issue(health_issue = issue):
     684                          return True
     685                  gmDispatcher.send (
     686                          signal = 'statustext',
     687                          msg = _('Cannot delete health issue.'),
     688                          beep = True
     689                  )
     690                  return False
     691          #-----------------------------------------
     692          def get_tooltip(data):
     693                  if data is None:
     694                          return None
     695                  patient = gmPerson.cPatient(data['pk_patient'])
     696                  return data.format (
     697                          patient = patient,
     698                          with_summary = True,
     699                          with_codes = True,
     700                          with_episodes = True,
     701                          with_encounters = True,
     702                          with_medications = False,
     703                          with_hospital_stays = False,
     704                          with_procedures = False,
     705                          with_family_history = False,
     706                          with_documents = False,
     707                          with_tests = False,
     708                          with_vaccinations = False
     709                  )
     710          #-----------------------------------------
     711          def refresh(lctrl):
     712                  issues = emr.get_health_issues()
     713                  items = [
     714                          [
     715                                  gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), '', ''),
     716                                  i['description'],
     717                                  gmTools.bool2subst(i['clinically_relevant'], _('relevant'), '', ''),
     718                                  gmTools.bool2subst(i['is_active'], _('active'), '', ''),
     719                                  gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), '', '')
     720                          ] for i in issues
     721                  ]
     722                  lctrl.set_string_items(items = items)
     723                  lctrl.set_data(data = issues)
     724          #-----------------------------------------
     725          return gmListWidgets.get_choices_from_list (
     726                  parent = parent,
     727                  msg = _('\nSelect the health issues !\n'),
     728                  caption = _('Showing health issues ...'),
     729                  columns = ['', _('Health issue'), '', '', ''],
     730                  single_selection = False,
     731                  edit_callback = edit,
     732                  new_callback = edit,
     733                  delete_callback = delete,
     734                  refresh_callback = refresh
     735          )
     736  #----------------------------------------------------------------
     737 -class cIssueListSelectorDlg(gmListWidgets.cGenericListSelectorDlg):
     738
     739          # FIXME: support pre-selection
     740
     741 -        def __init__(self, *args, **kwargs):
     742
     743                  issues = kwargs['issues']
     744                  del kwargs['issues']
     745
     746                  gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
     747
     748                  self.SetTitle(_('Select the health issues you are interested in ...'))
     749                  self._LCTRL_items.set_columns(['', _('Health Issue'), '', '', ''])
     750
     751                  for issue in issues:
     752                          if issue['is_confidential']:
     753                                  row_num = self._LCTRL_items.InsertItem(sys.maxsize, label = _('confidential'))
     754                                  self._LCTRL_items.SetItemTextColour(row_num, wx.Colour('RED'))
     755                          else:
     756                                  row_num = self._LCTRL_items.InsertItem(sys.maxsize, label = '')
     757
     758                          self._LCTRL_items.SetItem(index = row_num, column = 1, label = issue['description'])
     759                          if issue['clinically_relevant']:
     760                                  self._LCTRL_items.SetItem(index = row_num, column = 2, label = _('relevant'))
     761                          if issue['is_active']:
     762                                  self._LCTRL_items.SetItem(index = row_num, column = 3, label = _('active'))
     763                          if issue['is_cause_of_death']:
     764                                  self._LCTRL_items.SetItem(index = row_num, column = 4, label = _('fatal'))
     765
     766                  self._LCTRL_items.set_column_widths()
     767                  self._LCTRL_items.set_data(data = issues)
     768
     769  #----------------------------------------------------------------
     770 -class cIssueSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
     771          """Let the user select a health issue.
     772
     773          The user can select a health issue from the existing issues
     774          of a patient. Selection is done with a phrasewheel so the user
     775          can type the issue name and matches will be shown. Typing
     776          "*" will show the entire list of issues. Inactive issues
     777          will be marked as such. If the user types an issue name not
     778          in the list of existing issues a new issue can be created
     779          from it if the programmer activated that feature.
     780
     781          If keyword <patient_id> is set to None or left out the control
     782          will listen to patient change signals and therefore act on
     783          gmPerson.gmCurrentPatient() changes.
     784          """
     785 -        def __init__(self, *args, **kwargs):
     786
     787                  ctxt = {'ctxt_pat': {'where_part': 'pk_patient=%(pat)s', 'placeholder': 'pat'}}
     788
     789                  mp = gmMatchProvider.cMatchProvider_SQL2 (
     790                          # FIXME: consider clin.health_issue.clinically_relevant
     791                          queries = ["""
     792                                  SELECT
     793                                          data,
     794                                          field_label,
     795                                          list_label
     796                                  FROM ((
     797                                          SELECT
     798                                                  pk_health_issue AS data,
     799                                                  description AS field_label,
     800                                                  description AS list_label
     801                                          FROM clin.v_health_issues
     802                                          WHERE
     803                                                  is_active IS true
     804                                                          AND
     805                                                  description %(fragment_condition)s
     806                                                          AND
     807                                                  %(ctxt_pat)s
     808
     809                                          ) UNION (
     810
     811                                          SELECT
     812                                                  pk_health_issue AS data,
     813                                                  description AS field_label,
     814                                                  description || _(' (inactive)') AS list_label
     815                                          FROM clin.v_health_issues
     816                                          WHERE
     817                                                  is_active IS false
     818                                                          AND
     819                                                  description %(fragment_condition)s
     820                                                          AND
     821                                                  %(ctxt_pat)s
     822                                  )) AS union_query
     823                                  ORDER BY
     824                                          list_label"""
     825                          ],
     826                          context = ctxt
     827                  )
     828                  try: kwargs['patient_id']
     829                  except KeyError: kwargs['patient_id'] = None
     830
     831                  if kwargs['patient_id'] is None:
     832                          self.use_current_patient = True
     833                          self.__register_patient_change_signals()
     834                          pat = gmPerson.gmCurrentPatient()
     835                          if pat.connected:
     836                                  mp.set_context('pat', pat.ID)
     837                  else:
     838                          self.use_current_patient = False
     839                          self.__patient_id = int(kwargs['patient_id'])
     840                          mp.set_context('pat', self.__patient_id)
     841
     842                  del kwargs['patient_id']
     843
     844                  gmPhraseWheel.cPhraseWheel.__init__ (
     845                          self,
     846                          *args,
     847                          **kwargs
     848                  )
     849                  self.matcher = mp
     850          #--------------------------------------------------------
     851          # external API
     852          #--------------------------------------------------------
     853 -        def set_patient(self, patient_id=None):
     854                  if self.use_current_patient:
     855                          return False
     856                  self.__patient_id = int(patient_id)
     857                  self.set_context('pat', self.__patient_id)
     858                  return True
     859          #--------------------------------------------------------
     860 -        def _create_data(self):
     861                  issue_name = self.GetValue().strip()
     862                  if issue_name == '':
     863                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot create health issue without name.'), beep = True)
     864                          _log.debug('cannot create health issue without name')
     865                          return
     866
     867                  if self.use_current_patient:
     868                          pat = gmPerson.gmCurrentPatient()
     869                  else:
     870                          pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
     871
     872                  emr = pat.emr
     873                  issue = emr.add_health_issue(issue_name = issue_name)
     874
     875                  if issue is None:
     876                          self.data = {}
     877                  else:
     878                          self.SetText (
     879                                  value = issue_name,
     880                                  data = issue['pk_health_issue']
     881                          )
     882          #--------------------------------------------------------
     883 -        def _data2instance(self):
     884                  return gmEMRStructItems.cHealthIssue(aPK_obj = self.GetData())
     885          #--------------------------------------------------------
     886 -        def _get_data_tooltip(self):
     887                  if self.GetData() is None:
     888                          return None
     889                  issue = self._data2instance()
     890                  if issue is None:
     891                          return None
     892                  return issue.format (
     893                          patient = None,
     894                          with_summary = True,
     895                          with_codes = False,
     896                          with_episodes = True,
     897                          with_encounters = True,
     898                          with_medications = False,
     899                          with_hospital_stays = False,
     900                          with_procedures = False,
     901                          with_family_history = False,
     902                          with_documents = False,
     903                          with_tests = False,
     904                          with_vaccinations = False,
     905                          with_external_care = True
     906                  )
     907          #--------------------------------------------------------
     908          # internal API
     909          #--------------------------------------------------------
     910 -        def __register_patient_change_signals(self):
     911                  gmDispatcher.connect(self._pre_patient_unselection, 'pre_patient_unselection')
     912                  gmDispatcher.connect(self._post_patient_selection, 'post_patient_selection')
     913          #--------------------------------------------------------
     914 -        def _pre_patient_unselection(self):
     915                  return True
     916          #--------------------------------------------------------
     917 -        def _post_patient_selection(self):
     918                  if self.use_current_patient:
     919                          patient = gmPerson.gmCurrentPatient()
     920                          self.set_context('pat', patient.ID)
     921                  return True
     922  #------------------------------------------------------------
     923  from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
     924
     925 -class cIssueSelectionDlg(wxgIssueSelectionDlg.wxgIssueSelectionDlg):
     926
     927 -        def __init__(self, *args, **kwargs):
     928                  try:
     929                          msg = kwargs['message']
     930                  except KeyError:
     931                          msg = None
     932                  del kwargs['message']
     933                  wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
     934                  if msg is not None:
     935                          self._lbl_message.SetLabel(label=msg)
     936          #--------------------------------------------------------
     937 -        def _on_OK_button_pressed(self, event):
     938                  event.Skip()
     939                  pk_issue = self._PhWheel_issue.GetData(can_create=True)
     940                  if pk_issue is None:
     941                          gmGuiHelpers.gm_show_error (
     942                                  _('Cannot create new health issue:\n [%(issue)s]') % {'issue': self._PhWheel_issue.GetValue().strip()},
     943                                  _('Selecting health issue')
     944                          )
     945                          return False
     946                  return True
     947  #------------------------------------------------------------
     948  from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
     949
     950 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
     951          """Panel encapsulating health issue edit area functionality."""
     952
     953 -        def __init__(self, *args, **kwargs):
     954
     955                  try:
     956                          data = kwargs['issue']
     957                          del kwargs['issue']
     958                  except KeyError:
     959                          data = None
     960
     961                  wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
     962                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     963
     964                  self.mode = 'new'
     965                  self.data = data
     966                  if data is not None:
     967                          self.mode = 'edit'
     968
     969                  self.__init_ui()
     970          #----------------------------------------------------------------
     971 -        def __init_ui(self):
     972
     973                  # FIXME: include more sources: coding systems/other database columns
     974                  mp = gmMatchProvider.cMatchProvider_SQL2 (
     975                          queries = ["SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
     976                  )
     977                  mp.setThresholds(1, 3, 5)
     978                  self._PRW_condition.matcher = mp
     979
     980                  mp = gmMatchProvider.cMatchProvider_SQL2 (
     981                          queries = ["""
     982  SELECT DISTINCT ON (grouping) grouping, grouping from (
     983
     984          SELECT rank, grouping from ((
     985
     986                  SELECT
     987                          grouping,
     988                          1 as rank
     989                  from
     990                          clin.health_issue
     991                  where
     992                          grouping %%(fragment_condition)s
     993                                  and
     994                          (SELECT True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
     995
     996          ) union (
     997
     998                  SELECT
     999                          grouping,
    1000                          2 as rank
    1001                  from
    1002                          clin.health_issue
    1003                  where
    1004                          grouping %%(fragment_condition)s
    1005
    1006          )) as union_result
    1007
    1008          order by rank
    1009
    1010  ) as order_result
    1011
    1012  limit 50""" % gmPerson.gmCurrentPatient().ID
    1013                          ]
    1014                  )
    1015                  mp.setThresholds(1, 3, 5)
    1016                  self._PRW_grouping.matcher = mp
    1017
    1018                  self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
    1019                  self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
    1020
    1021  #               self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
    1022  #               self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
    1023
    1024                  self._PRW_year_noted.Enable(True)
    1025
    1026                  self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
    1027
    1028          #----------------------------------------------------------------
    1029          # generic Edit Area mixin API
    1030          #----------------------------------------------------------------
    1031 -        def _valid_for_save(self):
    1032
    1033                  if self._PRW_condition.GetValue().strip() == '':
    1034                          self._PRW_condition.display_as_valid(False)
    1035                          self._PRW_condition.SetFocus()
    1036                          return False
    1037                  self._PRW_condition.display_as_valid(True)
    1038                  self._PRW_condition.Refresh()
    1039
    1040                  # FIXME: sanity check age/year diagnosed
    1041                  age_noted = self._PRW_age_noted.GetValue().strip()
    1042                  if age_noted != '':
    1043                          if gmDateTime.str2interval(str_interval = age_noted) is None:
    1044                                  self._PRW_age_noted.display_as_valid(False)
    1045                                  self._PRW_age_noted.SetFocus()
    1046                                  return False
    1047                  self._PRW_age_noted.display_as_valid(True)
    1048                  return True
    1049
    1050          #----------------------------------------------------------------
    1051 -        def _save_as_new(self):
    1052                  pat = gmPerson.gmCurrentPatient()
    1053                  emr = pat.emr
    1054
    1055                  issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
    1056
    1057                  side = ''
    1058                  if self._ChBOX_left.GetValue():
    1059                          side += 's'
    1060                  if self._ChBOX_right.GetValue():
    1061                          side += 'd'
    1062                  issue['laterality'] = side
    1063
    1064                  issue['summary'] = self._TCTRL_status.GetValue().strip()
    1065                  issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
    1066                  issue['grouping'] = self._PRW_grouping.GetValue().strip()
    1067                  issue['is_active'] = self._ChBOX_active.GetValue()
    1068                  issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
    1069                  issue['is_confidential'] = self._ChBOX_confidential.GetValue()
    1070                  issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
    1071
    1072                  age_noted = self._PRW_age_noted.GetData()
    1073                  if age_noted is not None:
    1074                          issue['age_noted'] = age_noted
    1075
    1076                  issue.save()
    1077
    1078                  issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
    1079
    1080                  self.data = issue
    1081                  return True
    1082          #----------------------------------------------------------------
    1083 -        def _save_as_update(self):
    1084
    1085                  self.data['description'] = self._PRW_condition.GetValue().strip()
    1086
    1087                  side = ''
    1088                  if self._ChBOX_left.GetValue():
    1089                          side += 's'
    1090                  if self._ChBOX_right.GetValue():
    1091                          side += 'd'
    1092                  self.data['laterality'] = side
    1093
    1094                  self.data['summary'] = self._TCTRL_status.GetValue().strip()
    1095                  self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
    1096                  self.data['grouping'] = self._PRW_grouping.GetValue().strip()
    1097                  self.data['is_active'] = bool(self._ChBOX_active.GetValue())
    1098                  self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
    1099                  self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
    1100                  self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
    1101
    1102                  age_noted = self._PRW_age_noted.GetData()
    1103                  if age_noted is not None:
    1104                          self.data['age_noted'] = age_noted
    1105
    1106                  self.data.save()
    1107                  self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
    1108
    1109                  return True
    1110          #----------------------------------------------------------------
    1111 -        def _refresh_as_new(self):
    1112                  self._PRW_condition.SetText()
    1113                  self._ChBOX_left.SetValue(0)
    1114                  self._ChBOX_right.SetValue(0)
    1115                  self._PRW_codes.SetText()
    1116                  self._on_leave_codes()
    1117                  self._PRW_certainty.SetText()
    1118                  self._PRW_grouping.SetText()
    1119                  self._TCTRL_status.SetValue('')
    1120                  self._PRW_age_noted.SetText()
    1121                  self._PRW_year_noted.SetText()
    1122                  self._ChBOX_active.SetValue(1)
    1123                  self._ChBOX_relevant.SetValue(1)
    1124                  self._ChBOX_confidential.SetValue(0)
    1125                  self._ChBOX_caused_death.SetValue(0)
    1126
    1127                  self._PRW_condition.SetFocus()
    1128                  return True
    1129          #----------------------------------------------------------------
    1130 -        def _refresh_from_existing(self):
    1131                  self._PRW_condition.SetText(self.data['description'])
    1132
    1133                  lat = gmTools.coalesce(self.data['laterality'], '')
    1134                  if lat.find('s') == -1:
    1135                          self._ChBOX_left.SetValue(0)
    1136                  else:
    1137                          self._ChBOX_left.SetValue(1)
    1138                  if lat.find('d') == -1:
    1139                          self._ChBOX_right.SetValue(0)
    1140                  else:
    1141                          self._ChBOX_right.SetValue(1)
    1142
    1143                  val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
    1144                  self._PRW_codes.SetText(val, data)
    1145                  self._on_leave_codes()
    1146
    1147                  if self.data['diagnostic_certainty_classification'] is not None:
    1148                          self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
    1149                  self._PRW_grouping.SetText(gmTools.coalesce(self.data['grouping'], ''))
    1150                  self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], ''))
    1151
    1152                  if self.data['age_noted'] is None:
    1153                          self._PRW_age_noted.SetText()
    1154                  else:
    1155                          self._PRW_age_noted.SetText (
    1156                                  value = '%sd' % self.data['age_noted'].days,
    1157                                  data = self.data['age_noted']
    1158                          )
    1159
    1160                  self._ChBOX_active.SetValue(self.data['is_active'])
    1161                  self._ChBOX_relevant.SetValue(self.data['clinically_relevant'])
    1162                  self._ChBOX_confidential.SetValue(self.data['is_confidential'])
    1163                  self._ChBOX_caused_death.SetValue(self.data['is_cause_of_death'])
    1164
    1165                  self._TCTRL_status.SetFocus()
    1166
    1167                  return True
    1168          #----------------------------------------------------------------
    1169 -        def _refresh_as_new_from_existing(self):
    1170                  return self._refresh_as_new()
    1171          #--------------------------------------------------------
    1172          # internal helpers
    1173          #--------------------------------------------------------
    1174 -        def _on_leave_codes(self, *args, **kwargs):
    1175                  if not self._PRW_codes.IsModified():
    1176                          return True
    1177
    1178                  self._TCTRL_code_details.SetValue('- ' + '\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
    1179          #--------------------------------------------------------
    1180 -        def _on_leave_age_noted(self, *args, **kwargs):
    1181
    1182                  if not self._PRW_age_noted.IsModified():
    1183                          return True
    1184
    1185                  age_str = self._PRW_age_noted.GetValue().strip()
    1186
    1187                  if age_str == '':
    1188                          return True
    1189
    1190                  issue_age = gmDateTime.str2interval(str_interval = age_str)
    1191
    1192                  if issue_age is None:
    1193                          self.StatusText = _('Cannot parse [%s] into valid interval.') % age_str
    1194                          self._PRW_age_noted.display_as_valid(False)
    1195                          return True
    1196
    1197                  pat = gmPerson.gmCurrentPatient()
    1198                  if pat['dob'] is not None:
    1199                          max_issue_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
    1200                          if issue_age >= max_issue_age:
    1201                                  self.StatusText = _('Health issue cannot have been noted at age %s. Patient is only %s old.') % (issue_age, pat.get_medical_age())
    1202                                  self._PRW_age_noted.display_as_valid(False)
    1203                                  return True
    1204
    1205                  self._PRW_age_noted.display_as_valid(True)
    1206                  self._PRW_age_noted.SetText(value = age_str, data = issue_age)
    1207
    1208                  if pat['dob'] is not None:
    1209                          fts = gmDateTime.cFuzzyTimestamp (
    1210                                  timestamp = pat['dob'] + issue_age,
    1211                                  accuracy = gmDateTime.acc_months
    1212                          )
    1213                          self._PRW_year_noted.SetText(value = str(fts), data = fts)
    1214
    1215                  return True
    1216          #--------------------------------------------------------
    1217 -        def _on_leave_year_noted(self, *args, **kwargs):
    1218
    1219                  if not self._PRW_year_noted.IsModified():
    1220                          return True
    1221
    1222                  year_noted = self._PRW_year_noted.GetData()
    1223
    1224                  if year_noted is None:
    1225                          if self._PRW_year_noted.GetValue().strip() == '':
    1226                                  self._PRW_year_noted.display_as_valid(True)
    1227                                  return True
    1228                          self._PRW_year_noted.display_as_valid(False)
    1229                          return True
    1230
    1231                  year_noted = year_noted.get_pydt()
    1232
    1233                  if year_noted >= pydt.datetime.now(tz = year_noted.tzinfo):
    1234                          self.StatusText = _('Condition diagnosed in the future.')
    1235                          self._PRW_year_noted.display_as_valid(False)
    1236                          return True
    1237
    1238                  self._PRW_year_noted.display_as_valid(True)
    1239
    1240                  pat = gmPerson.gmCurrentPatient()
    1241                  if pat['dob'] is not None:
    1242                          issue_age = year_noted - pat['dob']
    1243                          age_str = gmDateTime.format_interval_medically(interval = issue_age)
    1244                          self._PRW_age_noted.SetText(age_str, issue_age, True)
    1245
    1246                  return True
    1247          #--------------------------------------------------------
    1248 -        def _on_modified_age_noted(self, *args, **kwargs):
    1249                  wx.CallAfter(self._PRW_year_noted.SetText, '', None, True)
    1250                  return True
    1251          #--------------------------------------------------------
    1252 -        def _on_modified_year_noted(self, *args, **kwargs):
    1253                  wx.CallAfter(self._PRW_age_noted.SetText, '', None, True)
    1254                  return True
    1255
    1256  #================================================================
    1257  # diagnostic certainty related widgets/functions
    1258  #----------------------------------------------------------------
    1259 -class cDiagnosticCertaintyClassificationPhraseWheel(gmPhraseWheel.cPhraseWheel):
    1260
    1261 -        def __init__(self, *args, **kwargs):
    1262
    1263                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
    1264
    1265                  self.selection_only = False                     # can be NULL, too
    1266
    1267                  mp = gmMatchProvider.cMatchProvider_FixedList (
    1268                          aSeq = [
    1269                                  {'data': 'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('A'), 'weight': 1},
    1270                                  {'data': 'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('B'), 'weight': 1},
    1271                                  {'data': 'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('C'), 'weight': 1},
    1272                                  {'data': 'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('D'), 'weight': 1}
    1273                          ]
    1274                  )
    1275                  mp.setThresholds(1, 2, 4)
    1276                  self.matcher = mp
    1277
    1278                  self.SetToolTip(_(
    1279                          "The diagnostic classification or grading of this assessment.\n"
    1280                          "\n"
    1281                          "This documents how certain one is about this being a true diagnosis."
    1282                  ))
    1283
    1284  #================================================================
    1285  # MAIN
    1286  #----------------------------------------------------------------
    1287  if __name__ == '__main__':
    1288
    1289          if len(sys.argv) < 2:
    1290                  sys.exit()
    1291
    1292          if sys.argv[1] != 'test':
    1293                  sys.exit()
    1294
    1295          from Gnumed.business import gmPersonSearch
    1296          from Gnumed.wxpython import gmPatSearchWidgets
    1297
    1298          #================================================================
    1299 -        class testapp (wx.App):
    1300                          """
    1301                          Test application for testing EMR struct widgets
    1302                          """
    1303                          #--------------------------------------------------------
    1304 -                        def OnInit (self):
    1305                                  """
    1306                                  Create test application UI
    1307                                  """
    1308                                  frame = wx.Frame (
    1309                                                          None,
    1310                                                          -4,
    1311                                                          'Testing EMR struct widgets',
    1312                                                          size=wx.Size(600, 400),
    1313                                                          style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
    1314                                                  )
    1315                                  filemenu = wx.Menu()
    1316                                  filemenu.AppendSeparator()
    1317                                  item = filemenu.Append(ID_EXIT, "E&xit"," Terminate test application")
    1318                                  self.Bind(wx.EVT_MENU, self.OnCloseWindow, item)
    1319
    1320                                  # Creating the menubar.
    1321                                  menuBar = wx.MenuBar()
    1322                                  menuBar.Append(filemenu,"&File")
    1323
    1324                                  frame.SetMenuBar(menuBar)
    1325
    1326                                  txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
    1327                                  wx.DefaultPosition, wx.DefaultSize, 0 )
    1328
    1329                                  # patient EMR
    1330                                  self.__pat = gmPerson.gmCurrentPatient()
    1331
    1332                                  frame.Show(1)
    1333                                  return 1
    1334                          #--------------------------------------------------------
    1335 -                        def OnCloseWindow (self, e):
    1336                                  """
    1337                                  Close test aplication
    1338                                  """
    1339                                  self.ExitMainLoop ()
    1340
    1341          #----------------------------------------------------------------
    1342 -        def test_epsiode_edit_area_pnl():
    1343                  app = wx.PyWidgetTester(size = (200, 300))
    1344                  emr = pat.emr
    1345                  epi = emr.get_episodes()[0]
    1346                  pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi)
    1347                  app.frame.Show(True)
    1348                  app.MainLoop()
    1349          #----------------------------------------------------------------
    1350 -        def test_episode_edit_area_dialog():
    1351                  app = wx.PyWidgetTester(size = (200, 300))
    1352                  emr = pat.emr
    1353                  epi = emr.get_episodes()[0]
    1354                  edit_episode(parent=app.frame, episode=epi)
    1355
    1356          #----------------------------------------------------------------
    1357 -        def test_episode_selection_prw():
    1358                  frame = wx.Frame()
    1359                  wx.GetApp().SetTopWindow(frame)
    1360                  prw = cEpisodeSelectionPhraseWheel(frame)
    1361                  #app.SetWidget()
    1362  #               app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(350,20), pos=(10,20), patient_id=pat.ID)
    1363                  frame.Show(True)
    1364                  wx.GetApp().MainLoop()
    1365
    1366          #----------------------------------------------------------------
    1367 -        def test_health_issue_edit_area_dlg():
    1368                  app = wx.PyWidgetTester(size = (200, 300))
    1369                  edit_health_issue(parent=app.frame, issue=None)
    1370
    1371          #----------------------------------------------------------------
    1372 -        def test_health_issue_edit_area_pnl():
    1373                  app = wx.PyWidgetTester(size = (200, 300))
    1374                  app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
    1375                  app.MainLoop()
    1376
    1377          #================================================================
    1378
    1379          branch = gmPraxis.get_praxis_branches()[0]
    1380          prax = gmPraxis.gmCurrentPraxisBranch(branch)
    1381          print(prax)
    1382
    1383          #app = wx.PyWidgetTester(size = (400, 40))
    1384          app = wx.App()#size = (400, 40))
    1385
    1386          # obtain patient
    1387          pat = gmPersonSearch.ask_for_patient()
    1388          if pat is None:
    1389                  print("No patient. Exiting gracefully...")
    1390                  sys.exit(0)
    1391          gmPatSearchWidgets.set_active_patient(patient=pat)
    1392
    1393          #test_epsiode_edit_area_pnl()
    1394          #test_episode_edit_area_dialog()
    1395          #test_health_issue_edit_area_dlg()
    1396          test_episode_selection_prw()
    1397