Package Gnumed :: Package business :: Module gmPerson
[frames] | no frames]

Source Code for Module Gnumed.business.gmPerson

   1  # -*- coding: utf8 -*- 
   2  """GNUmed patient objects. 
   3   
   4  This is a patient object intended to let a useful client-side 
   5  API crystallize from actual use in true XP fashion. 
   6  """ 
   7  #============================================================ 
   8  __version__ = "$Revision: 1.198 $" 
   9  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
  10  __license__ = "GPL" 
  11   
  12  # std lib 
  13  import sys, os.path, time, re as regex, string, types, datetime as pyDT, codecs, threading, logging 
  14   
  15   
  16  # GNUmed 
  17  if __name__ == '__main__': 
  18          sys.path.insert(0, '../../') 
  19  from Gnumed.pycommon import gmExceptions, gmDispatcher, gmBorg, gmI18N, gmNull, gmBusinessDBObject, gmTools 
  20  from Gnumed.pycommon import gmPG2, gmMatchProvider, gmDateTime 
  21  from Gnumed.pycommon import gmLog2 
  22  from Gnumed.pycommon import gmHooks 
  23   
  24  from Gnumed.business import gmDemographicRecord, gmProviderInbox, gmXdtMappings, gmClinicalRecord 
  25  from Gnumed.business.gmDocuments import cDocumentFolder 
  26   
  27   
  28  _log = logging.getLogger('gm.person') 
  29  _log.info(__version__) 
  30   
  31  __gender_list = None 
  32  __gender_idx = None 
  33   
  34  __gender2salutation_map = None 
  35   
  36  #============================================================ 
  37  # FIXME: make this work as a mapping type, too 
38 -class cDTO_person(object):
39
40 - def __init__(self):
41 self.identity = None 42 self.external_ids = [] 43 self.comm_channels = [] 44 self.addresses = []
45 #-------------------------------------------------------- 46 # external API 47 #--------------------------------------------------------
48 - def keys(self):
49 return 'firstnames lastnames dob gender'.split()
50 #--------------------------------------------------------
51 - def delete_from_source(self):
52 pass
53 #--------------------------------------------------------
54 - def get_candidate_identities(self, can_create=False):
55 """Generate generic queries. 56 57 - not locale dependant 58 - data -> firstnames, lastnames, dob, gender 59 60 shall we mogrify name parts ? probably not as external 61 sources should know what they do 62 63 finds by inactive name, too, but then shows 64 the corresponding active name ;-) 65 66 Returns list of matching identities (may be empty) 67 or None if it was told to create an identity but couldn't. 68 """ 69 where_snippets = [] 70 args = {} 71 72 where_snippets.append(u'firstnames = %(first)s') 73 args['first'] = self.firstnames 74 75 where_snippets.append(u'lastnames = %(last)s') 76 args['last'] = self.lastnames 77 78 if self.dob is not None: 79 where_snippets.append(u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %(dob)s)") 80 args['dob'] = self.dob.replace(hour = 23, minute = 59, second = 59) 81 82 if self.gender is not None: 83 where_snippets.append('gender = %(sex)s') 84 args['sex'] = self.gender 85 86 cmd = u""" 87 SELECT *, '%s' AS match_type 88 FROM dem.v_basic_person 89 WHERE 90 pk_identity IN ( 91 SELECT pk_identity FROM dem.v_person_names WHERE %s 92 ) 93 ORDER BY lastnames, firstnames, dob""" % ( 94 _('external patient source (name, gender, date of birth)'), 95 ' AND '.join(where_snippets) 96 ) 97 98 try: 99 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx=True) 100 except: 101 _log.error(u'cannot get candidate identities for dto "%s"' % self) 102 _log.exception('query %s' % cmd) 103 rows = [] 104 105 if len(rows) == 0: 106 _log.debug('no candidate identity matches found') 107 if not can_create: 108 return [] 109 ident = self.import_into_database() 110 if ident is None: 111 return None 112 identities = [ident] 113 else: 114 identities = [ cIdentity(row = {'pk_field': 'pk_identity', 'data': row, 'idx': idx}) for row in rows ] 115 116 return identities
117 #--------------------------------------------------------
118 - def import_into_database(self):
119 """Imports self into the database.""" 120 121 self.identity = create_identity ( 122 firstnames = self.firstnames, 123 lastnames = self.lastnames, 124 gender = self.gender, 125 dob = self.dob 126 ) 127 128 if self.identity is None: 129 return None 130 131 for ext_id in self.external_ids: 132 try: 133 self.identity.add_external_id ( 134 type_name = ext_id['name'], 135 value = ext_id['value'], 136 issuer = ext_id['issuer'], 137 comment = ext_id['comment'] 138 ) 139 except StandardError: 140 _log.exception('cannot import <external ID> from external data source') 141 _log.log_stack_trace() 142 143 for comm in self.comm_channels: 144 try: 145 self.identity.link_comm_channel ( 146 comm_medium = comm['channel'], 147 url = comm['url'] 148 ) 149 except StandardError: 150 _log.exception('cannot import <comm channel> from external data source') 151 _log.log_stack_trace() 152 153 for adr in self.addresses: 154 try: 155 self.identity.link_address ( 156 number = adr['number'], 157 street = adr['street'], 158 postcode = adr['zip'], 159 urb = adr['urb'], 160 state = adr['region'], 161 country = adr['country'] 162 ) 163 except StandardError: 164 _log.exception('cannot import <address> from external data source') 165 _log.log_stack_trace() 166 167 return self.identity
168 #--------------------------------------------------------
169 - def import_extra_data(self, *args, **kwargs):
170 pass
171 #--------------------------------------------------------
172 - def remember_external_id(self, name=None, value=None, issuer=None, comment=None):
173 value = value.strip() 174 if value == u'': 175 return 176 name = name.strip() 177 if name == u'': 178 raise ValueError(_('<name> cannot be empty')) 179 issuer = issuer.strip() 180 if issuer == u'': 181 raise ValueError(_('<issuer> cannot be empty')) 182 self.external_ids.append({'name': name, 'value': value, 'issuer': issuer, 'comment': comment})
183 #--------------------------------------------------------
184 - def remember_comm_channel(self, channel=None, url=None):
185 url = url.strip() 186 if url == u'': 187 return 188 channel = channel.strip() 189 if channel == u'': 190 raise ValueError(_('<channel> cannot be empty')) 191 self.comm_channels.append({'channel': channel, 'url': url})
192 #--------------------------------------------------------
193 - def remember_address(self, number=None, street=None, urb=None, region=None, zip=None, country=None):
194 number = number.strip() 195 if number == u'': 196 raise ValueError(_('<number> cannot be empty')) 197 street = street.strip() 198 if street == u'': 199 raise ValueError(_('<street> cannot be empty')) 200 urb = urb.strip() 201 if urb == u'': 202 raise ValueError(_('<urb> cannot be empty')) 203 zip = zip.strip() 204 if zip == u'': 205 raise ValueError(_('<zip> cannot be empty')) 206 country = country.strip() 207 if country == u'': 208 raise ValueError(_('<country> cannot be empty')) 209 region = region.strip() 210 if region == u'': 211 region = u'??' 212 self.addresses.append ({ 213 u'number': number, 214 u'street': street, 215 u'zip': zip, 216 u'urb': urb, 217 u'region': region, 218 u'country': country 219 })
220 #-------------------------------------------------------- 221 # customizing behaviour 222 #--------------------------------------------------------
223 - def __str__(self):
224 return u'<%s @ %s: %s %s (%s) %s>' % ( 225 self.__class__.__name__, 226 id(self), 227 self.firstnames, 228 self.lastnames, 229 self.gender, 230 self.dob 231 )
232 #--------------------------------------------------------
233 - def __setattr__(self, attr, val):
234 """Do some sanity checks on self.* access.""" 235 236 if attr == 'gender': 237 glist, idx = get_gender_list() 238 for gender in glist: 239 if str(val) in [gender[0], gender[1], gender[2], gender[3]]: 240 val = gender[idx['tag']] 241 object.__setattr__(self, attr, val) 242 return 243 raise ValueError('invalid gender: [%s]' % val) 244 245 if attr == 'dob': 246 if val is not None: 247 if not isinstance(val, pyDT.datetime): 248 raise TypeError('invalid type for DOB (must be datetime.datetime): %s [%s]' % (type(val), val)) 249 if val.tzinfo is None: 250 raise ValueError('datetime.datetime instance is lacking a time zone: [%s]' % val.isoformat()) 251 252 object.__setattr__(self, attr, val) 253 return
254 #--------------------------------------------------------
255 - def __getitem__(self, attr):
256 return getattr(self, attr)
257 #============================================================
258 -class cPersonName(gmBusinessDBObject.cBusinessDBObject):
259 _cmd_fetch_payload = u"SELECT * FROM dem.v_person_names WHERE pk_name = %s" 260 _cmds_store_payload = [ 261 u"""UPDATE dem.names SET 262 active = FALSE 263 WHERE 264 %(active_name)s IS TRUE -- act only when needed and only 265 AND 266 id_identity = %(pk_identity)s -- on names of this identity 267 AND 268 active IS TRUE -- which are active 269 AND 270 id != %(pk_name)s -- but NOT *this* name 271 """, 272 u"""update dem.names set 273 active = %(active_name)s, 274 preferred = %(preferred)s, 275 comment = %(comment)s 276 where 277 id = %(pk_name)s and 278 id_identity = %(pk_identity)s and -- belt and suspenders 279 xmin = %(xmin_name)s""", 280 u"""select xmin as xmin_name from dem.names where id = %(pk_name)s""" 281 ] 282 _updatable_fields = ['active_name', 'preferred', 'comment'] 283 #--------------------------------------------------------
284 - def __setitem__(self, attribute, value):
285 if attribute == 'active_name': 286 # cannot *directly* deactivate a name, only indirectly 287 # by activating another one 288 # FIXME: should be done at DB level 289 if self._payload[self._idx['active_name']] is True: 290 return 291 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
292 #--------------------------------------------------------
293 - def _get_description(self):
294 return '%(last)s, %(title)s %(first)s%(nick)s' % { 295 'last': self._payload[self._idx['lastnames']], 296 'title': gmTools.coalesce ( 297 self._payload[self._idx['title']], 298 map_gender2salutation(self._payload[self._idx['gender']]) 299 ), 300 'first': self._payload[self._idx['firstnames']], 301 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u' "%s"', u'%s') 302 }
303 304 description = property(_get_description, lambda x:x)
305 #============================================================
306 -class cStaff(gmBusinessDBObject.cBusinessDBObject):
307 _cmd_fetch_payload = u"SELECT * FROM dem.v_staff WHERE pk_staff = %s" 308 _cmds_store_payload = [ 309 u"""UPDATE dem.staff SET 310 fk_role = %(pk_role)s, 311 short_alias = %(short_alias)s, 312 comment = gm.nullify_empty_string(%(comment)s), 313 is_active = %(is_active)s, 314 db_user = %(db_user)s 315 WHERE 316 pk = %(pk_staff)s 317 AND 318 xmin = %(xmin_staff)s 319 RETURNING 320 xmin AS xmin_staff""" 321 ] 322 _updatable_fields = ['pk_role', 'short_alias', 'comment', 'is_active', 'db_user'] 323 #--------------------------------------------------------
324 - def __init__(self, aPK_obj=None, row=None):
325 # by default get staff corresponding to CURRENT_USER 326 if (aPK_obj is None) and (row is None): 327 cmd = u"select * from dem.v_staff where db_user = CURRENT_USER" 328 try: 329 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True) 330 except: 331 _log.exception('cannot instantiate staff instance') 332 gmLog2.log_stack_trace() 333 raise ValueError('cannot instantiate staff instance for database account CURRENT_USER') 334 if len(rows) == 0: 335 raise ValueError('no staff record for database account CURRENT_USER') 336 row = { 337 'pk_field': 'pk_staff', 338 'idx': idx, 339 'data': rows[0] 340 } 341 gmBusinessDBObject.cBusinessDBObject.__init__(self, row = row) 342 else: 343 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj = aPK_obj, row = row) 344 345 # are we SELF ? 346 self.__is_current_user = (gmPG2.get_current_user() == self._payload[self._idx['db_user']]) 347 348 self.__inbox = None
349 #--------------------------------------------------------
350 - def __setitem__(self, attribute, value):
351 if attribute == 'db_user': 352 if self.__is_current_user: 353 _log.debug('will not modify database account association of CURRENT_USER staff member') 354 return 355 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
356 #--------------------------------------------------------
357 - def _get_db_lang(self):
358 rows, idx = gmPG2.run_ro_queries ( 359 queries = [{ 360 'cmd': u'select i18n.get_curr_lang(%(usr)s)', 361 'args': {'usr': self._payload[self._idx['db_user']]} 362 }] 363 ) 364 return rows[0][0]
365
366 - def _set_db_lang(self, language):
367 if not gmPG2.set_user_language(language = language): 368 raise ValueError ( 369 u'Cannot set database language to [%s] for user [%s].' % (language, self._payload[self._idx['db_user']]) 370 ) 371 return
372 373 database_language = property(_get_db_lang, _set_db_lang) 374 #--------------------------------------------------------
375 - def _get_inbox(self):
376 if self.__inbox is None: 377 self.__inbox = gmProviderInbox.cProviderInbox(provider_id = self._payload[self._idx['pk_staff']]) 378 return self.__inbox
379
380 - def _set_inbox(self, inbox):
381 return
382 383 inbox = property(_get_inbox, _set_inbox)
384 #============================================================
385 -def set_current_provider_to_logged_on_user():
386 gmCurrentProvider(provider = cStaff())
387 #============================================================
388 -class gmCurrentProvider(gmBorg.cBorg):
389 """Staff member Borg to hold currently logged on provider. 390 391 There may be many instances of this but they all share state. 392 """
393 - def __init__(self, provider=None):
394 """Change or get currently logged on provider. 395 396 provider: 397 * None: get copy of current instance 398 * cStaff instance: change logged on provider (role) 399 """ 400 # make sure we do have a provider pointer 401 try: 402 self.provider 403 except AttributeError: 404 self.provider = gmNull.cNull() 405 406 # user wants copy of currently logged on provider 407 if provider is None: 408 return None 409 410 # must be cStaff instance, then 411 if not isinstance(provider, cStaff): 412 raise ValueError, 'cannot set logged on provider to [%s], must be either None or cStaff instance' % str(provider) 413 414 # same ID, no change needed 415 if self.provider['pk_staff'] == provider['pk_staff']: 416 return None 417 418 # first invocation 419 if isinstance(self.provider, gmNull.cNull): 420 self.provider = provider 421 return None 422 423 # user wants different provider 424 raise ValueError, 'provider change [%s] -> [%s] not yet supported' % (self.provider['pk_staff'], provider['pk_staff'])
425 426 #--------------------------------------------------------
427 - def get_staff(self):
428 return self.provider
429 #-------------------------------------------------------- 430 # __getitem__ handling 431 #--------------------------------------------------------
432 - def __getitem__(self, aVar):
433 """Return any attribute if known how to retrieve it by proxy. 434 """ 435 return self.provider[aVar]
436 #-------------------------------------------------------- 437 # __s/getattr__ handling 438 #--------------------------------------------------------
439 - def __getattr__(self, attribute):
440 if attribute == 'provider': # so we can __init__ ourselves 441 raise AttributeError 442 if not isinstance(self.provider, gmNull.cNull): 443 return getattr(self.provider, attribute)
444 # raise AttributeError 445 #============================================================
446 -class cIdentity(gmBusinessDBObject.cBusinessDBObject):
447 _cmd_fetch_payload = u"select * from dem.v_basic_person where pk_identity = %s" 448 _cmds_store_payload = [ 449 u"""update dem.identity set 450 gender = %(gender)s, 451 dob = %(dob)s, 452 tob = %(tob)s, 453 cob = gm.nullify_empty_string(%(cob)s), 454 title = gm.nullify_empty_string(%(title)s), 455 fk_marital_status = %(pk_marital_status)s, 456 karyotype = gm.nullify_empty_string(%(karyotype)s), 457 pupic = gm.nullify_empty_string(%(pupic)s), 458 deceased = %(deceased)s, 459 emergency_contact = gm.nullify_empty_string(%(emergency_contact)s), 460 fk_emergency_contact = %(pk_emergency_contact)s, 461 fk_primary_provider = %(pk_primary_provider)s, 462 comment = gm.nullify_empty_string(%(comment)s) 463 where 464 pk = %(pk_identity)s and 465 xmin = %(xmin_identity)s""", 466 u"""select xmin_identity from dem.v_basic_person where pk_identity = %(pk_identity)s""" 467 ] 468 _updatable_fields = [ 469 "title", 470 "dob", 471 "tob", 472 "cob", 473 "gender", 474 "pk_marital_status", 475 "karyotype", 476 "pupic", 477 'deceased', 478 'emergency_contact', 479 'pk_emergency_contact', 480 'pk_primary_provider', 481 'comment' 482 ] 483 #--------------------------------------------------------
484 - def _get_ID(self):
485 return self._payload[self._idx['pk_identity']]
486 - def _set_ID(self, value):
487 raise AttributeError('setting ID of identity is not allowed')
488 ID = property(_get_ID, _set_ID) 489 #--------------------------------------------------------
490 - def __setitem__(self, attribute, value):
491 492 if attribute == 'dob': 493 if value is not None: 494 495 if isinstance(value, pyDT.datetime): 496 if value.tzinfo is None: 497 raise ValueError('datetime.datetime instance is lacking a time zone: [%s]' % dt.isoformat()) 498 else: 499 raise TypeError, '[%s]: type [%s] (%s) invalid for attribute [dob], must be datetime.datetime or None' % (self.__class__.__name__, type(value), value) 500 501 # compare DOB at seconds level 502 if self._payload[self._idx['dob']] is not None: 503 old_dob = gmDateTime.pydt_strftime ( 504 self._payload[self._idx['dob']], 505 format = '%Y %m %d %H %M %S', 506 accuracy = gmDateTime.acc_seconds 507 ) 508 new_dob = gmDateTime.pydt_strftime ( 509 value, 510 format = '%Y %m %d %H %M %S', 511 accuracy = gmDateTime.acc_seconds 512 ) 513 if new_dob == old_dob: 514 return 515 516 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
517 #--------------------------------------------------------
518 - def cleanup(self):
519 pass
520 #--------------------------------------------------------
521 - def _get_is_patient(self):
522 cmd = u""" 523 select exists ( 524 select 1 525 from clin.v_emr_journal 526 where 527 pk_patient = %(pat)s 528 and 529 soap_cat is not null 530 )""" 531 args = {'pat': self._payload[self._idx['pk_identity']]} 532 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 533 return rows[0][0]
534
535 - def _set_is_patient(self, value):
536 raise AttributeError('setting is_patient status of identity is not allowed')
537 538 is_patient = property(_get_is_patient, _set_is_patient) 539 #-------------------------------------------------------- 540 # identity API 541 #--------------------------------------------------------
542 - def get_active_name(self):
543 for name in self.get_names(): 544 if name['active_name'] is True: 545 return name 546 547 _log.error('cannot retrieve active name for patient [%s]' % self._payload[self._idx['pk_identity']]) 548 return None
549 #--------------------------------------------------------
550 - def get_names(self):
551 cmd = u"select * from dem.v_person_names where pk_identity = %(pk_pat)s" 552 rows, idx = gmPG2.run_ro_queries ( 553 queries = [{ 554 'cmd': cmd, 555 'args': {'pk_pat': self._payload[self._idx['pk_identity']]} 556 }], 557 get_col_idx = True 558 ) 559 560 if len(rows) == 0: 561 # no names registered for patient 562 return [] 563 564 names = [ cPersonName(row = {'idx': idx, 'data': r, 'pk_field': 'pk_name'}) for r in rows ] 565 return names
566 #--------------------------------------------------------
567 - def get_formatted_dob(self, format='%x', encoding=None, none_string=None):
568 if self._payload[self._idx['dob']] is None: 569 if none_string is None: 570 return _('** DOB unknown **') 571 return none_string 572 573 return gmDateTime.pydt_strftime ( 574 self._payload[self._idx['dob']], 575 format = format, 576 encoding = encoding, 577 accuracy = gmDateTime.acc_days 578 )
579 #--------------------------------------------------------
580 - def get_description_gender(self):
581 return '%(sex)s%(title)s %(last)s, %(first)s%(nick)s' % { 582 'last': self._payload[self._idx['lastnames']], 583 'first': self._payload[self._idx['firstnames']], 584 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u' (%s)', u'%s'), 585 'sex': map_gender2salutation(self._payload[self._idx['gender']]), 586 'title': gmTools.coalesce(self._payload[self._idx['title']], u'', u' %s', u'%s') 587 }
588 #--------------------------------------------------------
589 - def get_description(self):
590 return '%(last)s,%(title)s %(first)s%(nick)s' % { 591 'last': self._payload[self._idx['lastnames']], 592 'title': gmTools.coalesce(self._payload[self._idx['title']], u'', u' %s', u'%s'), 593 'first': self._payload[self._idx['firstnames']], 594 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u' (%s)', u'%s') 595 }
596 #--------------------------------------------------------
597 - def add_name(self, firstnames, lastnames, active=True):
598 """Add a name. 599 600 @param firstnames The first names. 601 @param lastnames The last names. 602 @param active When True, the new name will become the active one (hence setting other names to inactive) 603 @type active A types.BooleanType instance 604 """ 605 name = create_name(self.ID, firstnames, lastnames, active) 606 if active: 607 self.refetch_payload() 608 return name
609 #--------------------------------------------------------
610 - def delete_name(self, name=None):
611 cmd = u"delete from dem.names where id = %(name)s and id_identity = %(pat)s" 612 args = {'name': name['pk_name'], 'pat': self.ID} 613 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
614 # can't have been the active name as that would raise an 615 # exception (since no active name would be left) so no 616 # data refetch needed 617 #--------------------------------------------------------
618 - def set_nickname(self, nickname=None):
619 """ 620 Set the nickname. Setting the nickname only makes sense for the currently 621 active name. 622 @param nickname The preferred/nick/warrior name to set. 623 """ 624 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': u"select dem.set_nickname(%s, %s)", 'args': [self.ID, nickname]}]) 625 self.refetch_payload() 626 return True
627 #--------------------------------------------------------
628 - def get_tags(self, order_by=None):
629 if order_by is None: 630 order_by = u'' 631 else: 632 order_by = u'ORDER BY %s' % order_by 633 634 cmd = gmDemographicRecord._SQL_get_identity_tags % (u'pk_identity = %%(pat)s %s' % order_by) 635 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {u'pat': self.ID}}], get_col_idx = True) 636 637 return [ gmDemographicRecord.cIdentityTag(row = {'data': r, 'idx': idx, 'pk_field': 'pk_identity_tag'}) for r in rows ]
638 #--------------------------------------------------------
639 - def add_tag(self, tag):
640 args = { 641 u'tag': tag, 642 u'identity': self.ID 643 } 644 645 # already exists ? 646 cmd = u"SELECT pk FROM dem.identity_tag WHERE fk_tag = %(tag)s AND fk_identity = %(identity)s" 647 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 648 if len(rows) > 0: 649 return gmDemographicRecord.cIdentityTag(aPK_obj = rows[0]['pk']) 650 651 # no, add 652 cmd = u""" 653 INSERT INTO dem.identity_tag ( 654 fk_tag, 655 fk_identity 656 ) VALUES ( 657 %(tag)s, 658 %(identity)s 659 ) 660 RETURNING pk 661 """ 662 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False) 663 return gmDemographicRecord.cIdentityTag(aPK_obj = rows[0]['pk'])
664 #--------------------------------------------------------
665 - def remove_tag(self, tag):
666 cmd = u"DELETE FROM dem.identity_tag WHERE pk = %(pk)s" 667 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': tag}}])
668 #-------------------------------------------------------- 669 # external ID API 670 # 671 # since external IDs are not treated as first class 672 # citizens (classes in their own right, that is), we 673 # handle them *entirely* within cIdentity, also they 674 # only make sense with one single person (like names) 675 # and are not reused (like addresses), so they are 676 # truly added/deleted, not just linked/unlinked 677 #--------------------------------------------------------
678 - def add_external_id(self, type_name=None, value=None, issuer=None, comment=None, pk_type=None):
679 """Adds an external ID to the patient. 680 681 creates ID type if necessary 682 """ 683 684 # check for existing ID 685 if pk_type is not None: 686 cmd = u""" 687 select * from dem.v_external_ids4identity where 688 pk_identity = %(pat)s and 689 pk_type = %(pk_type)s and 690 value = %(val)s""" 691 else: 692 # by type/value/issuer 693 if issuer is None: 694 cmd = u""" 695 select * from dem.v_external_ids4identity where 696 pk_identity = %(pat)s and 697 name = %(name)s and 698 value = %(val)s""" 699 else: 700 cmd = u""" 701 select * from dem.v_external_ids4identity where 702 pk_identity = %(pat)s and 703 name = %(name)s and 704 value = %(val)s and 705 issuer = %(issuer)s""" 706 args = { 707 'pat': self.ID, 708 'name': type_name, 709 'val': value, 710 'issuer': issuer, 711 'pk_type': pk_type 712 } 713 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 714 715 # create new ID if not found 716 if len(rows) == 0: 717 718 args = { 719 'pat': self.ID, 720 'val': value, 721 'type_name': type_name, 722 'pk_type': pk_type, 723 'issuer': issuer, 724 'comment': comment 725 } 726 727 if pk_type is None: 728 cmd = u"""insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values ( 729 %(val)s, 730 (select dem.add_external_id_type(%(type_name)s, %(issuer)s)), 731 %(comment)s, 732 %(pat)s 733 )""" 734 else: 735 cmd = u"""insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values ( 736 %(val)s, 737 %(pk_type)s, 738 %(comment)s, 739 %(pat)s 740 )""" 741 742 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 743 744 # or update comment of existing ID 745 else: 746 row = rows[0] 747 if comment is not None: 748 # comment not already there ? 749 if gmTools.coalesce(row['comment'], '').find(comment.strip()) == -1: 750 comment = '%s%s' % (gmTools.coalesce(row['comment'], '', '%s // '), comment.strip) 751 cmd = u"update dem.lnk_identity2ext_id set comment = %(comment)s where id=%(pk)s" 752 args = {'comment': comment, 'pk': row['pk_id']} 753 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
754 #--------------------------------------------------------
755 - def update_external_id(self, pk_id=None, type=None, value=None, issuer=None, comment=None):
756 """Edits an existing external ID. 757 758 creates ID type if necessary 759 """ 760 cmd = u""" 761 update dem.lnk_identity2ext_id set 762 fk_origin = (select dem.add_external_id_type(%(type)s, %(issuer)s)), 763 external_id = %(value)s, 764 comment = %(comment)s 765 where id = %(pk)s""" 766 args = {'pk': pk_id, 'value': value, 'type': type, 'issuer': issuer, 'comment': comment} 767 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
768 #--------------------------------------------------------
769 - def get_external_ids(self, id_type=None, issuer=None):
770 where_parts = ['pk_identity = %(pat)s'] 771 args = {'pat': self.ID} 772 773 if id_type is not None: 774 where_parts.append(u'name = %(name)s') 775 args['name'] = id_type.strip() 776 777 if issuer is not None: 778 where_parts.append(u'issuer = %(issuer)s') 779 args['issuer'] = issuer.strip() 780 781 cmd = u"select * from dem.v_external_ids4identity where %s" % ' and '.join(where_parts) 782 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 783 784 return rows
785 #--------------------------------------------------------
786 - def delete_external_id(self, pk_ext_id=None):
787 cmd = u""" 788 delete from dem.lnk_identity2ext_id 789 where id_identity = %(pat)s and id = %(pk)s""" 790 args = {'pat': self.ID, 'pk': pk_ext_id} 791 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
792 #--------------------------------------------------------
793 - def assimilate_identity(self, other_identity=None, link_obj=None):
794 """Merge another identity into this one. 795 796 Keep this one. Delete other one.""" 797 798 if other_identity.ID == self.ID: 799 return True, None 800 801 curr_pat = gmCurrentPatient() 802 if curr_pat.connected: 803 if other_identity.ID == curr_pat.ID: 804 return False, _('Cannot merge active patient into another patient.') 805 806 queries = [] 807 args = {'old_pat': other_identity.ID, 'new_pat': self.ID} 808 809 # delete old allergy state 810 queries.append ({ 811 'cmd': u'delete from clin.allergy_state where pk = (select pk_allergy_state from clin.v_pat_allergy_state where pk_patient = %(old_pat)s)', 812 'args': args 813 }) 814 # FIXME: adjust allergy_state in kept patient 815 816 # deactivate all names of old patient 817 queries.append ({ 818 'cmd': u'update dem.names set active = False where id_identity = %(old_pat)s', 819 'args': args 820 }) 821 822 # find FKs pointing to identity 823 FKs = gmPG2.get_foreign_keys2column ( 824 schema = u'dem', 825 table = u'identity', 826 column = u'pk' 827 ) 828 829 # generate UPDATEs 830 cmd_template = u'update %s set %s = %%(new_pat)s where %s = %%(old_pat)s' 831 for FK in FKs: 832 queries.append ({ 833 'cmd': cmd_template % (FK['referencing_table'], FK['referencing_column'], FK['referencing_column']), 834 'args': args 835 }) 836 837 # remove old identity entry 838 queries.append ({ 839 'cmd': u'delete from dem.identity where pk = %(old_pat)s', 840 'args': args 841 }) 842 843 _log.warning('identity [%s] is about to assimilate identity [%s]', self.ID, other_identity.ID) 844 845 gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, end_tx = True) 846 847 self.add_external_id ( 848 type_name = u'merged GNUmed identity primary key', 849 value = u'GNUmed::pk::%s' % other_identity.ID, 850 issuer = u'GNUmed' 851 ) 852 853 return True, None
854 #-------------------------------------------------------- 855 #--------------------------------------------------------
856 - def put_on_waiting_list(self, urgency=0, comment=None, zone=None):
857 cmd = u""" 858 insert into clin.waiting_list (fk_patient, urgency, comment, area, list_position) 859 values ( 860 %(pat)s, 861 %(urg)s, 862 %(cmt)s, 863 %(area)s, 864 (select coalesce((max(list_position) + 1), 1) from clin.waiting_list) 865 )""" 866 args = {'pat': self.ID, 'urg': urgency, 'cmt': comment, 'area': zone} 867 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], verbose=True)
868 #--------------------------------------------------------
869 - def export_as_gdt(self, filename=None, encoding='iso-8859-15', external_id_type=None):
870 871 template = u'%s%s%s\r\n' 872 873 file = codecs.open ( 874 filename = filename, 875 mode = 'wb', 876 encoding = encoding, 877 errors = 'strict' 878 ) 879 880 file.write(template % (u'013', u'8000', u'6301')) 881 file.write(template % (u'013', u'9218', u'2.10')) 882 if external_id_type is None: 883 file.write(template % (u'%03d' % (9 + len(str(self.ID))), u'3000', self.ID)) 884 else: 885 ext_ids = self.get_external_ids(id_type = external_id_type) 886 if len(ext_ids) > 0: 887 file.write(template % (u'%03d' % (9 + len(ext_ids[0]['value'])), u'3000', ext_ids[0]['value'])) 888 file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['lastnames']])), u'3101', self._payload[self._idx['lastnames']])) 889 file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['firstnames']])), u'3102', self._payload[self._idx['firstnames']])) 890 file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['dob']].strftime('%d%m%Y'))), u'3103', self._payload[self._idx['dob']].strftime('%d%m%Y'))) 891 file.write(template % (u'010', u'3110', gmXdtMappings.map_gender_gm2xdt[self._payload[self._idx['gender']]])) 892 file.write(template % (u'025', u'6330', 'GNUmed::9206::encoding')) 893 file.write(template % (u'%03d' % (9 + len(encoding)), u'6331', encoding)) 894 if external_id_type is None: 895 file.write(template % (u'029', u'6332', u'GNUmed::3000::source')) 896 file.write(template % (u'017', u'6333', u'internal')) 897 else: 898 if len(ext_ids) > 0: 899 file.write(template % (u'029', u'6332', u'GNUmed::3000::source')) 900 file.write(template % (u'%03d' % (9 + len(external_id_type)), u'6333', external_id_type)) 901 902 file.close()
903 #-------------------------------------------------------- 904 # occupations API 905 #--------------------------------------------------------
906 - def get_occupations(self):
907 cmd = u"select * from dem.v_person_jobs where pk_identity=%s" 908 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}]) 909 return rows
910 #-------------------------------------------------------- 947 #-------------------------------------------------------- 955 #-------------------------------------------------------- 956 # comms API 957 #--------------------------------------------------------
958 - def get_comm_channels(self, comm_medium=None):
959 cmd = u"select * from dem.v_person_comms where pk_identity = %s" 960 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx = True) 961 962 filtered = rows 963 964 if comm_medium is not None: 965 filtered = [] 966 for row in rows: 967 if row['comm_type'] == comm_medium: 968 filtered.append(row) 969 970 return [ gmDemographicRecord.cCommChannel(row = { 971 'pk_field': 'pk_lnk_identity2comm', 972 'data': r, 973 'idx': idx 974 }) for r in filtered 975 ]
976 #-------------------------------------------------------- 994 #-------------------------------------------------------- 1000 #-------------------------------------------------------- 1001 # contacts API 1002 #--------------------------------------------------------
1003 - def get_addresses(self, address_type=None):
1004 cmd = u"select * from dem.v_pat_addresses where pk_identity=%s" 1005 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx=True) 1006 addresses = [] 1007 for r in rows: 1008 addresses.append(gmDemographicRecord.cPatientAddress(row={'idx': idx, 'data': r, 'pk_field': 'pk_address'})) 1009 1010 filtered = addresses 1011 1012 if address_type is not None: 1013 filtered = [] 1014 for adr in addresses: 1015 if adr['address_type'] == address_type: 1016 filtered.append(adr) 1017 1018 return filtered
1019 #-------------------------------------------------------- 1067 #---------------------------------------------------------------------- 1077 #---------------------------------------------------------------------- 1078 # relatives API 1079 #----------------------------------------------------------------------
1080 - def get_relatives(self):
1081 cmd = u""" 1082 select 1083 t.description, 1084 vbp.pk_identity as id, 1085 title, 1086 firstnames, 1087 lastnames, 1088 dob, 1089 cob, 1090 gender, 1091 karyotype, 1092 pupic, 1093 pk_marital_status, 1094 marital_status, 1095 xmin_identity, 1096 preferred 1097 from 1098 dem.v_basic_person vbp, dem.relation_types t, dem.lnk_person2relative l 1099 where 1100 ( 1101 l.id_identity = %(pk)s and 1102 vbp.pk_identity = l.id_relative and 1103 t.id = l.id_relation_type 1104 ) or ( 1105 l.id_relative = %(pk)s and 1106 vbp.pk_identity = l.id_identity and 1107 t.inverse = l.id_relation_type 1108 )""" 1109 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 1110 if len(rows) == 0: 1111 return [] 1112 return [(row[0], cIdentity(row = {'data': row[1:], 'idx':idx, 'pk_field': 'pk'})) for row in rows]
1113 #-------------------------------------------------------- 1133 #----------------------------------------------------------------------
1134 - def delete_relative(self, relation):
1135 # unlink only, don't delete relative itself 1136 self.set_relative(None, relation)
1137 #--------------------------------------------------------
1139 if self._payload[self._idx['pk_emergency_contact']] is None: 1140 return None 1141 return cIdentity(aPK_obj = self._payload[self._idx['pk_emergency_contact']])
1142 1143 emergency_contact_in_database = property(_get_emergency_contact_from_database, lambda x:x) 1144 #---------------------------------------------------------------------- 1145 # age/dob related 1146 #----------------------------------------------------------------------
1147 - def get_medical_age(self):
1148 dob = self['dob'] 1149 1150 if dob is None: 1151 return u'??' 1152 1153 if self['deceased'] is None: 1154 # return gmDateTime.format_interval_medically ( 1155 # pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone) - dob 1156 # ) 1157 return gmDateTime.format_apparent_age_medically ( 1158 age = gmDateTime.calculate_apparent_age(start = dob) 1159 ) 1160 1161 return u'%s%s' % ( 1162 gmTools.u_latin_cross, 1163 # gmDateTime.format_interval_medically(self['deceased'] - dob) 1164 gmDateTime.format_apparent_age_medically ( 1165 age = gmDateTime.calculate_apparent_age ( 1166 start = dob, 1167 end = self['deceased'] 1168 ) 1169 ) 1170 )
1171 #----------------------------------------------------------------------
1172 - def dob_in_range(self, min_distance=u'1 week', max_distance=u'1 week'):
1173 cmd = u'select dem.dob_is_in_range(%(dob)s, %(min)s, %(max)s)' 1174 rows, idx = gmPG2.run_ro_queries ( 1175 queries = [{ 1176 'cmd': cmd, 1177 'args': {'dob': self['dob'], 'min': min_distance, 'max': max_distance} 1178 }] 1179 ) 1180 return rows[0][0]
1181 #---------------------------------------------------------------------- 1182 # practice related 1183 #----------------------------------------------------------------------
1184 - def get_last_encounter(self):
1185 cmd = u'select * from clin.v_most_recent_encounters where pk_patient=%s' 1186 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self._payload[self._idx['pk_identity']]]}]) 1187 if len(rows) > 0: 1188 return rows[0] 1189 else: 1190 return None
1191 #--------------------------------------------------------
1192 - def _get_messages(self):
1193 return gmProviderInbox.get_inbox_messages(pk_patient = self._payload[self._idx['pk_identity']])
1194
1195 - def _set_messages(self, messages):
1196 return
1197 1198 messages = property(_get_messages, _set_messages) 1199 #--------------------------------------------------------
1200 - def delete_message(self, pk=None):
1201 return gmProviderInbox.delete_inbox_message(inbox_message = pk)
1202 #--------------------------------------------------------
1203 - def _get_primary_provider(self):
1204 if self._payload[self._idx['pk_primary_provider']] is None: 1205 return None 1206 return cStaff(aPK_obj = self._payload[self._idx['pk_primary_provider']])
1207 1208 primary_provider = property(_get_primary_provider, lambda x:x) 1209 #---------------------------------------------------------------------- 1210 # convenience 1211 #----------------------------------------------------------------------
1212 - def get_dirname(self):
1213 """Format patient demographics into patient specific path name fragment.""" 1214 return '%s-%s%s-%s' % ( 1215 self._payload[self._idx['lastnames']].replace(u' ', u'_'), 1216 self._payload[self._idx['firstnames']].replace(u' ', u'_'), 1217 gmTools.coalesce(self._payload[self._idx['preferred']], u'', template_initial = u'-(%s)'), 1218 self.get_formatted_dob(format = '%Y-%m-%d', encoding = gmI18N.get_encoding()) 1219 )
1220 #============================================================
1221 -class cStaffMember(cIdentity):
1222 """Represents a staff member which is a person. 1223 1224 - a specializing subclass of cIdentity turning it into a staff member 1225 """
1226 - def __init__(self, identity = None):
1227 cIdentity.__init__(self, identity=identity) 1228 self.__db_cache = {}
1229 #--------------------------------------------------------
1230 - def get_inbox(self):
1231 return gmProviderInbox.cProviderInbox(provider_id = self.ID)
1232 #============================================================
1233 -class cPatient(cIdentity):
1234 """Represents a person which is a patient. 1235 1236 - a specializing subclass of cIdentity turning it into a patient 1237 - its use is to cache subobjects like EMR and document folder 1238 """
1239 - def __init__(self, aPK_obj=None, row=None):
1240 cIdentity.__init__(self, aPK_obj=aPK_obj, row=row) 1241 self.__db_cache = {} 1242 self.__emr_access_lock = threading.Lock()
1243 #--------------------------------------------------------
1244 - def cleanup(self):
1245 """Do cleanups before dying. 1246 1247 - note that this may be called in a thread 1248 """ 1249 if self.__db_cache.has_key('clinical record'): 1250 self.__db_cache['clinical record'].cleanup() 1251 if self.__db_cache.has_key('document folder'): 1252 self.__db_cache['document folder'].cleanup() 1253 cIdentity.cleanup(self)
1254 #----------------------------------------------------------
1255 - def get_emr(self):
1256 if not self.__emr_access_lock.acquire(False): 1257 raise AttributeError('cannot access EMR') 1258 try: 1259 emr = self.__db_cache['clinical record'] 1260 self.__emr_access_lock.release() 1261 return emr 1262 except KeyError: 1263 pass 1264 1265 self.__db_cache['clinical record'] = gmClinicalRecord.cClinicalRecord(aPKey = self._payload[self._idx['pk_identity']]) 1266 self.__emr_access_lock.release() 1267 return self.__db_cache['clinical record']
1268 #--------------------------------------------------------
1269 - def get_document_folder(self):
1270 try: 1271 return self.__db_cache['document folder'] 1272 except KeyError: 1273 pass 1274 1275 self.__db_cache['document folder'] = cDocumentFolder(aPKey = self._payload[self._idx['pk_identity']]) 1276 return self.__db_cache['document folder']
1277 #============================================================
1278 -class gmCurrentPatient(gmBorg.cBorg):
1279 """Patient Borg to hold currently active patient. 1280 1281 There may be many instances of this but they all share state. 1282 """
1283 - def __init__(self, patient=None, forced_reload=False):
1284 """Change or get currently active patient. 1285 1286 patient: 1287 * None: get currently active patient 1288 * -1: unset currently active patient 1289 * cPatient instance: set active patient if possible 1290 """ 1291 # make sure we do have a patient pointer 1292 try: 1293 tmp = self.patient 1294 except AttributeError: 1295 self.patient = gmNull.cNull() 1296 self.__register_interests() 1297 # set initial lock state, 1298 # this lock protects against activating another patient 1299 # when we are controlled from a remote application 1300 self.__lock_depth = 0 1301 # initialize callback state 1302 self.__pre_selection_callbacks = [] 1303 1304 # user wants copy of current patient 1305 if patient is None: 1306 return None 1307 1308 # do nothing if patient is locked 1309 if self.locked: 1310 _log.error('patient [%s] is locked, cannot change to [%s]' % (self.patient['pk_identity'], patient)) 1311 return None 1312 1313 # user wants to explicitly unset current patient 1314 if patient == -1: 1315 _log.debug('explicitly unsetting current patient') 1316 if not self.__run_pre_selection_callbacks(): 1317 _log.debug('not unsetting current patient') 1318 return None 1319 self.__send_pre_selection_notification() 1320 self.patient.cleanup() 1321 self.patient = gmNull.cNull() 1322 self.__send_selection_notification() 1323 return None 1324 1325 # must be cPatient instance, then 1326 if not isinstance(patient, cPatient): 1327 _log.error('cannot set active patient to [%s], must be either None, -1 or cPatient instance' % str(patient)) 1328 raise TypeError, 'gmPerson.gmCurrentPatient.__init__(): <patient> must be None, -1 or cPatient instance but is: %s' % str(patient) 1329 1330 # same ID, no change needed 1331 if (self.patient['pk_identity'] == patient['pk_identity']) and not forced_reload: 1332 return None 1333 1334 # user wants different patient 1335 _log.debug('patient change [%s] -> [%s] requested', self.patient['pk_identity'], patient['pk_identity']) 1336 1337 # everything seems swell 1338 if not self.__run_pre_selection_callbacks(): 1339 _log.debug('not changing current patient') 1340 return None 1341 self.__send_pre_selection_notification() 1342 self.patient.cleanup() 1343 self.patient = patient 1344 self.patient.get_emr() 1345 self.__send_selection_notification() 1346 1347 return None
1348 #--------------------------------------------------------
1349 - def __register_interests(self):
1350 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_identity_change) 1351 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_identity_change)
1352 #--------------------------------------------------------
1353 - def _on_identity_change(self):
1354 """Listen for patient *data* change.""" 1355 self.patient.refetch_payload()
1356 #-------------------------------------------------------- 1357 # external API 1358 #--------------------------------------------------------
1359 - def register_pre_selection_callback(self, callback=None):
1360 if not callable(callback): 1361 raise TypeError(u'callback [%s] not callable' % callback) 1362 1363 self.__pre_selection_callbacks.append(callback)
1364 #--------------------------------------------------------
1365 - def _get_connected(self):
1366 return (not isinstance(self.patient, gmNull.cNull))
1367
1368 - def _set_connected(self):
1369 raise AttributeError(u'invalid to set <connected> state')
1370 1371 connected = property(_get_connected, _set_connected) 1372 #--------------------------------------------------------
1373 - def _get_locked(self):
1374 return (self.__lock_depth > 0)
1375
1376 - def _set_locked(self, locked):
1377 if locked: 1378 self.__lock_depth = self.__lock_depth + 1 1379 gmDispatcher.send(signal='patient_locked') 1380 else: 1381 if self.__lock_depth == 0: 1382 _log.error('lock/unlock imbalance, trying to refcount lock depth below 0') 1383 return 1384 else: 1385 self.__lock_depth = self.__lock_depth - 1 1386 gmDispatcher.send(signal='patient_unlocked')
1387 1388 locked = property(_get_locked, _set_locked) 1389 #--------------------------------------------------------
1390 - def force_unlock(self):
1391 _log.info('forced patient unlock at lock depth [%s]' % self.__lock_depth) 1392 self.__lock_depth = 0 1393 gmDispatcher.send(signal='patient_unlocked')
1394 #-------------------------------------------------------- 1395 # patient change handling 1396 #--------------------------------------------------------
1398 if isinstance(self.patient, gmNull.cNull): 1399 return True 1400 1401 for call_back in self.__pre_selection_callbacks: 1402 try: 1403 successful = call_back() 1404 except: 1405 _log.exception('callback [%s] failed', call_back) 1406 print "*** pre-selection callback failed ***" 1407 print type(call_back) 1408 print call_back 1409 return False 1410 1411 if not successful: 1412 _log.debug('callback [%s] returned False', call_back) 1413 return False 1414 1415 return True
1416 #--------------------------------------------------------
1418 """Sends signal when another patient is about to become active. 1419 1420 This does NOT wait for signal handlers to complete. 1421 """ 1422 kwargs = { 1423 'signal': u'pre_patient_selection', 1424 'sender': id(self.__class__), 1425 'pk_identity': self.patient['pk_identity'] 1426 } 1427 gmDispatcher.send(**kwargs)
1428 #--------------------------------------------------------
1430 """Sends signal when another patient has actually been made active.""" 1431 kwargs = { 1432 'signal': u'post_patient_selection', 1433 'sender': id(self.__class__), 1434 'pk_identity': self.patient['pk_identity'] 1435 } 1436 gmDispatcher.send(**kwargs)
1437 #-------------------------------------------------------- 1438 # __getattr__ handling 1439 #--------------------------------------------------------
1440 - def __getattr__(self, attribute):
1441 if attribute == 'patient': 1442 raise AttributeError 1443 if not isinstance(self.patient, gmNull.cNull): 1444 return getattr(self.patient, attribute)
1445 #-------------------------------------------------------- 1446 # __get/setitem__ handling 1447 #--------------------------------------------------------
1448 - def __getitem__(self, attribute = None):
1449 """Return any attribute if known how to retrieve it by proxy. 1450 """ 1451 return self.patient[attribute]
1452 #--------------------------------------------------------
1453 - def __setitem__(self, attribute, value):
1454 self.patient[attribute] = value
1455 #============================================================ 1456 # match providers 1457 #============================================================
1458 -class cMatchProvider_Provider(gmMatchProvider.cMatchProvider_SQL2):
1459 - def __init__(self):
1460 gmMatchProvider.cMatchProvider_SQL2.__init__( 1461 self, 1462 queries = [ 1463 u"""select 1464 pk_staff, 1465 short_alias || ' (' || coalesce(title, '') || firstnames || ' ' || lastnames || ')', 1466 1 1467 from dem.v_staff 1468 where 1469 is_active and ( 1470 short_alias %(fragment_condition)s or 1471 firstnames %(fragment_condition)s or 1472 lastnames %(fragment_condition)s or 1473 db_user %(fragment_condition)s 1474 )""" 1475 ] 1476 ) 1477 self.setThresholds(1, 2, 3)
1478 #============================================================ 1479 # convenience functions 1480 #============================================================
1481 -def create_name(pk_person, firstnames, lastnames, active=False):
1482 queries = [{ 1483 'cmd': u"select dem.add_name(%s, %s, %s, %s)", 1484 'args': [pk_person, firstnames, lastnames, active] 1485 }] 1486 rows, idx = gmPG2.run_rw_queries(queries=queries, return_data=True) 1487 name = cPersonName(aPK_obj = rows[0][0]) 1488 return name
1489 #============================================================
1490 -def create_identity(gender=None, dob=None, lastnames=None, firstnames=None):
1491 1492 cmd1 = u"""INSERT INTO dem.identity (gender, dob) VALUES (%s, %s)""" 1493 cmd2 = u""" 1494 INSERT INTO dem.names ( 1495 id_identity, lastnames, firstnames 1496 ) VALUES ( 1497 currval('dem.identity_pk_seq'), coalesce(%s, 'xxxDEFAULTxxx'), coalesce(%s, 'xxxDEFAULTxxx') 1498 ) RETURNING id_identity""" 1499 rows, idx = gmPG2.run_rw_queries ( 1500 queries = [ 1501 {'cmd': cmd1, 'args': [gender, dob]}, 1502 {'cmd': cmd2, 'args': [lastnames, firstnames]} 1503 ], 1504 return_data = True 1505 ) 1506 ident = cIdentity(aPK_obj=rows[0][0]) 1507 gmHooks.run_hook_script(hook = u'post_person_creation') 1508 return ident
1509 #============================================================
1510 -def create_dummy_identity():
1511 cmd = u"INSERT INTO dem.identity(gender) VALUES ('xxxDEFAULTxxx') RETURNING pk" 1512 rows, idx = gmPG2.run_rw_queries ( 1513 queries = [{'cmd': cmd}], 1514 return_data = True 1515 ) 1516 return gmDemographicRecord.cIdentity(aPK_obj = rows[0][0])
1517 #============================================================
1518 -def set_active_patient(patient=None, forced_reload=False):
1519 """Set active patient. 1520 1521 If patient is -1 the active patient will be UNset. 1522 """ 1523 if isinstance(patient, cPatient): 1524 pat = patient 1525 elif isinstance(patient, cIdentity): 1526 pat = cPatient(aPK_obj=patient['pk_identity']) 1527 elif isinstance(patient, cStaff): 1528 pat = cPatient(aPK_obj=patient['pk_identity']) 1529 elif isinstance(patient, gmCurrentPatient): 1530 pat = patient.patient 1531 elif patient == -1: 1532 pat = patient 1533 else: 1534 raise ValueError('<patient> must be either -1, cPatient, cStaff, cIdentity or gmCurrentPatient instance, is: %s' % patient) 1535 1536 # attempt to switch 1537 try: 1538 gmCurrentPatient(patient = pat, forced_reload = forced_reload) 1539 except: 1540 _log.exception('error changing active patient to [%s]' % patient) 1541 return False 1542 1543 return True
1544 #============================================================ 1545 # gender related 1546 #------------------------------------------------------------
1547 -def get_gender_list():
1548 """Retrieves the list of known genders from the database.""" 1549 global __gender_idx 1550 global __gender_list 1551 1552 if __gender_list is None: 1553 cmd = u"select tag, l10n_tag, label, l10n_label, sort_weight from dem.v_gender_labels order by sort_weight desc" 1554 __gender_list, __gender_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 1555 1556 return (__gender_list, __gender_idx)
1557 #------------------------------------------------------------ 1558 map_gender2mf = { 1559 'm': u'm', 1560 'f': u'f', 1561 'tf': u'f', 1562 'tm': u'm', 1563 'h': u'mf' 1564 } 1565 #------------------------------------------------------------ 1566 # Maps GNUmed related i18n-aware gender specifiers to a unicode symbol. 1567 map_gender2symbol = { 1568 'm': u'\u2642', 1569 'f': u'\u2640', 1570 'tf': u'\u26A5\u2640', 1571 'tm': u'\u26A5\u2642', 1572 'h': u'\u26A5' 1573 # 'tf': u'\u2642\u2640-\u2640', 1574 # 'tm': u'\u2642\u2640-\u2642', 1575 # 'h': u'\u2642\u2640' 1576 } 1577 #------------------------------------------------------------
1578 -def map_gender2salutation(gender=None):
1579 """Maps GNUmed related i18n-aware gender specifiers to a human-readable salutation.""" 1580 1581 global __gender2salutation_map 1582 1583 if __gender2salutation_map is None: 1584 genders, idx = get_gender_list() 1585 __gender2salutation_map = { 1586 'm': _('Mr'), 1587 'f': _('Mrs'), 1588 'tf': u'', 1589 'tm': u'', 1590 'h': u'' 1591 } 1592 for g in genders: 1593 __gender2salutation_map[g[idx['l10n_tag']]] = __gender2salutation_map[g[idx['tag']]] 1594 __gender2salutation_map[g[idx['label']]] = __gender2salutation_map[g[idx['tag']]] 1595 __gender2salutation_map[g[idx['l10n_label']]] = __gender2salutation_map[g[idx['tag']]] 1596 1597 return __gender2salutation_map[gender]
1598 #------------------------------------------------------------
1599 -def map_firstnames2gender(firstnames=None):
1600 """Try getting the gender for the given first name.""" 1601 1602 if firstnames is None: 1603 return None 1604 1605 rows, idx = gmPG2.run_ro_queries(queries = [{ 1606 'cmd': u"select gender from dem.name_gender_map where name ilike %(fn)s limit 1", 1607 'args': {'fn': firstnames} 1608 }]) 1609 1610 if len(rows) == 0: 1611 return None 1612 1613 return rows[0][0]
1614 #============================================================
1615 -def get_staff_list(active_only=False):
1616 if active_only: 1617 cmd = u"select * from dem.v_staff where is_active order by can_login desc, short_alias asc" 1618 else: 1619 cmd = u"select * from dem.v_staff order by can_login desc, is_active desc, short_alias asc" 1620 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True) 1621 staff_list = [] 1622 for row in rows: 1623 obj_row = { 1624 'idx': idx, 1625 'data': row, 1626 'pk_field': 'pk_staff' 1627 } 1628 staff_list.append(cStaff(row=obj_row)) 1629 return staff_list
1630 #============================================================
1631 -def get_persons_from_pks(pks=None):
1632 return [ cIdentity(aPK_obj = pk) for pk in pks ]
1633 #============================================================
1634 -def get_person_from_xdt(filename=None, encoding=None, dob_format=None):
1635 from Gnumed.business import gmXdtObjects 1636 return gmXdtObjects.read_person_from_xdt(filename=filename, encoding=encoding, dob_format=dob_format)
1637 #============================================================
1638 -def get_persons_from_pracsoft_file(filename=None, encoding='ascii'):
1639 from Gnumed.business import gmPracSoftAU 1640 return gmPracSoftAU.read_persons_from_pracsoft_file(filename=filename, encoding=encoding)
1641 #============================================================ 1642 # main/testing 1643 #============================================================ 1644 if __name__ == '__main__': 1645 1646 if len(sys.argv) == 1: 1647 sys.exit() 1648 1649 if sys.argv[1] != 'test': 1650 sys.exit() 1651 1652 import datetime 1653 1654 gmI18N.activate_locale() 1655 gmI18N.install_domain() 1656 gmDateTime.init() 1657 1658 #--------------------------------------------------------
1659 - def test_set_active_pat():
1660 1661 ident = cIdentity(1) 1662 print "setting active patient with", ident 1663 set_active_patient(patient=ident) 1664 1665 patient = cPatient(12) 1666 print "setting active patient with", patient 1667 set_active_patient(patient=patient) 1668 1669 pat = gmCurrentPatient() 1670 print pat['dob'] 1671 #pat['dob'] = 'test' 1672 1673 staff = cStaff() 1674 print "setting active patient with", staff 1675 set_active_patient(patient=staff) 1676 1677 print "setting active patient with -1" 1678 set_active_patient(patient=-1)
1679 #--------------------------------------------------------
1680 - def test_dto_person():
1681 dto = cDTO_person() 1682 dto.firstnames = 'Sepp' 1683 dto.lastnames = 'Herberger' 1684 dto.gender = 'male' 1685 dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone) 1686 print dto 1687 1688 print dto['firstnames'] 1689 print dto['lastnames'] 1690 print dto['gender'] 1691 print dto['dob'] 1692 1693 for key in dto.keys(): 1694 print key
1695 #--------------------------------------------------------
1696 - def test_staff():
1697 staff = cStaff() 1698 print staff 1699 print staff.inbox 1700 print staff.inbox.messages
1701 #--------------------------------------------------------
1702 - def test_current_provider():
1703 staff = cStaff() 1704 provider = gmCurrentProvider(provider = staff) 1705 print provider 1706 print provider.inbox 1707 print provider.inbox.messages 1708 print provider.database_language 1709 tmp = provider.database_language 1710 provider.database_language = None 1711 print provider.database_language 1712 provider.database_language = tmp 1713 print provider.database_language
1714 #--------------------------------------------------------
1715 - def test_identity():
1716 # create patient 1717 print '\n\nCreating identity...' 1718 new_identity = create_identity(gender='m', dob='2005-01-01', lastnames='test lastnames', firstnames='test firstnames') 1719 print 'Identity created: %s' % new_identity 1720 1721 print '\nSetting title and gender...' 1722 new_identity['title'] = 'test title'; 1723 new_identity['gender'] = 'f'; 1724 new_identity.save_payload() 1725 print 'Refetching identity from db: %s' % cIdentity(aPK_obj=new_identity['pk_identity']) 1726 1727 print '\nGetting all names...' 1728 for a_name in new_identity.get_names(): 1729 print a_name 1730 print 'Active name: %s' % (new_identity.get_active_name()) 1731 print 'Setting nickname...' 1732 new_identity.set_nickname(nickname='test nickname') 1733 print 'Refetching all names...' 1734 for a_name in new_identity.get_names(): 1735 print a_name 1736 print 'Active name: %s' % (new_identity.get_active_name()) 1737 1738 print '\nIdentity occupations: %s' % new_identity['occupations'] 1739 print 'Creating identity occupation...' 1740 new_identity.link_occupation('test occupation') 1741 print 'Identity occupations: %s' % new_identity['occupations'] 1742 1743 print '\nIdentity addresses: %s' % new_identity.get_addresses() 1744 print 'Creating identity address...' 1745 # make sure the state exists in the backend 1746 new_identity.link_address ( 1747 number = 'test 1234', 1748 street = 'test street', 1749 postcode = 'test postcode', 1750 urb = 'test urb', 1751 state = 'SN', 1752 country = 'DE' 1753 ) 1754 print 'Identity addresses: %s' % new_identity.get_addresses() 1755 1756 print '\nIdentity communications: %s' % new_identity.get_comm_channels() 1757 print 'Creating identity communication...' 1758 new_identity.link_comm_channel('homephone', '1234566') 1759 print 'Identity communications: %s' % new_identity.get_comm_channels()
1760 #--------------------------------------------------------
1761 - def test_name():
1762 for pk in range(1,16): 1763 name = cPersonName(aPK_obj=pk) 1764 print name.description 1765 print ' ', name
1766 #-------------------------------------------------------- 1767 #test_dto_person() 1768 #test_identity() 1769 #test_set_active_pat() 1770 #test_search_by_dto() 1771 #test_staff() 1772 test_current_provider() 1773 #test_name() 1774 1775 #map_gender2salutation('m') 1776 # module functions 1777 #genders, idx = get_gender_list() 1778 #print "\n\nRetrieving gender enum (tag, label, weight):" 1779 #for gender in genders: 1780 # print "%s, %s, %s" % (gender[idx['tag']], gender[idx['l10n_label']], gender[idx['sort_weight']]) 1781 1782 #comms = get_comm_list() 1783 #print "\n\nRetrieving communication media enum (id, description): %s" % comms 1784 1785 #============================================================ 1786