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 return gmDateTime.format_dob ( 569 self._payload[self._idx['dob']], 570 format = format, 571 encoding = encoding, 572 none_string = none_string 573 )
574 #--------------------------------------------------------
575 - def get_description_gender(self):
576 return '%(sex)s%(title)s %(last)s, %(first)s%(nick)s' % { 577 'last': self._payload[self._idx['lastnames']], 578 'first': self._payload[self._idx['firstnames']], 579 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u' (%s)', u'%s'), 580 'sex': map_gender2salutation(self._payload[self._idx['gender']]), 581 'title': gmTools.coalesce(self._payload[self._idx['title']], u'', u' %s', u'%s') 582 }
583 #--------------------------------------------------------
584 - def get_description(self):
585 return '%(last)s,%(title)s %(first)s%(nick)s' % { 586 'last': self._payload[self._idx['lastnames']], 587 'title': gmTools.coalesce(self._payload[self._idx['title']], u'', u' %s', u'%s'), 588 'first': self._payload[self._idx['firstnames']], 589 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u' (%s)', u'%s') 590 }
591 #--------------------------------------------------------
592 - def add_name(self, firstnames, lastnames, active=True):
593 """Add a name. 594 595 @param firstnames The first names. 596 @param lastnames The last names. 597 @param active When True, the new name will become the active one (hence setting other names to inactive) 598 @type active A types.BooleanType instance 599 """ 600 name = create_name(self.ID, firstnames, lastnames, active) 601 if active: 602 self.refetch_payload() 603 return name
604 #--------------------------------------------------------
605 - def delete_name(self, name=None):
606 cmd = u"delete from dem.names where id = %(name)s and id_identity = %(pat)s" 607 args = {'name': name['pk_name'], 'pat': self.ID} 608 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
609 # can't have been the active name as that would raise an 610 # exception (since no active name would be left) so no 611 # data refetch needed 612 #--------------------------------------------------------
613 - def set_nickname(self, nickname=None):
614 """ 615 Set the nickname. Setting the nickname only makes sense for the currently 616 active name. 617 @param nickname The preferred/nick/warrior name to set. 618 """ 619 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': u"select dem.set_nickname(%s, %s)", 'args': [self.ID, nickname]}]) 620 self.refetch_payload() 621 return True
622 #--------------------------------------------------------
623 - def get_tags(self, order_by=None):
624 if order_by is None: 625 order_by = u'' 626 else: 627 order_by = u'ORDER BY %s' % order_by 628 629 cmd = gmDemographicRecord._SQL_get_identity_tags % (u'pk_identity = %%(pat)s %s' % order_by) 630 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {u'pat': self.ID}}], get_col_idx = True) 631 632 return [ gmDemographicRecord.cIdentityTag(row = {'data': r, 'idx': idx, 'pk_field': 'pk_identity_tag'}) for r in rows ]
633 #--------------------------------------------------------
634 - def add_tag(self, tag):
635 args = { 636 u'tag': tag, 637 u'identity': self.ID 638 } 639 640 # already exists ? 641 cmd = u"SELECT pk FROM dem.identity_tag WHERE fk_tag = %(tag)s AND fk_identity = %(identity)s" 642 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 643 if len(rows) > 0: 644 return gmDemographicRecord.cIdentityTag(aPK_obj = rows[0]['pk']) 645 646 # no, add 647 cmd = u""" 648 INSERT INTO dem.identity_tag ( 649 fk_tag, 650 fk_identity 651 ) VALUES ( 652 %(tag)s, 653 %(identity)s 654 ) 655 RETURNING pk 656 """ 657 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False) 658 return gmDemographicRecord.cIdentityTag(aPK_obj = rows[0]['pk'])
659 #--------------------------------------------------------
660 - def remove_tag(self, tag):
661 cmd = u"DELETE FROM dem.identity_tag WHERE pk = %(pk)s" 662 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': tag}}])
663 #-------------------------------------------------------- 664 # external ID API 665 # 666 # since external IDs are not treated as first class 667 # citizens (classes in their own right, that is), we 668 # handle them *entirely* within cIdentity, also they 669 # only make sense with one single person (like names) 670 # and are not reused (like addresses), so they are 671 # truly added/deleted, not just linked/unlinked 672 #--------------------------------------------------------
673 - def add_external_id(self, type_name=None, value=None, issuer=None, comment=None, pk_type=None):
674 """Adds an external ID to the patient. 675 676 creates ID type if necessary 677 """ 678 679 # check for existing ID 680 if pk_type is not None: 681 cmd = u""" 682 select * from dem.v_external_ids4identity where 683 pk_identity = %(pat)s and 684 pk_type = %(pk_type)s and 685 value = %(val)s""" 686 else: 687 # by type/value/issuer 688 if issuer is None: 689 cmd = u""" 690 select * from dem.v_external_ids4identity where 691 pk_identity = %(pat)s and 692 name = %(name)s and 693 value = %(val)s""" 694 else: 695 cmd = u""" 696 select * from dem.v_external_ids4identity where 697 pk_identity = %(pat)s and 698 name = %(name)s and 699 value = %(val)s and 700 issuer = %(issuer)s""" 701 args = { 702 'pat': self.ID, 703 'name': type_name, 704 'val': value, 705 'issuer': issuer, 706 'pk_type': pk_type 707 } 708 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 709 710 # create new ID if not found 711 if len(rows) == 0: 712 713 args = { 714 'pat': self.ID, 715 'val': value, 716 'type_name': type_name, 717 'pk_type': pk_type, 718 'issuer': issuer, 719 'comment': comment 720 } 721 722 if pk_type is None: 723 cmd = u"""insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values ( 724 %(val)s, 725 (select dem.add_external_id_type(%(type_name)s, %(issuer)s)), 726 %(comment)s, 727 %(pat)s 728 )""" 729 else: 730 cmd = u"""insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values ( 731 %(val)s, 732 %(pk_type)s, 733 %(comment)s, 734 %(pat)s 735 )""" 736 737 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 738 739 # or update comment of existing ID 740 else: 741 row = rows[0] 742 if comment is not None: 743 # comment not already there ? 744 if gmTools.coalesce(row['comment'], '').find(comment.strip()) == -1: 745 comment = '%s%s' % (gmTools.coalesce(row['comment'], '', '%s // '), comment.strip) 746 cmd = u"update dem.lnk_identity2ext_id set comment = %(comment)s where id=%(pk)s" 747 args = {'comment': comment, 'pk': row['pk_id']} 748 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
749 #--------------------------------------------------------
750 - def update_external_id(self, pk_id=None, type=None, value=None, issuer=None, comment=None):
751 """Edits an existing external ID. 752 753 creates ID type if necessary 754 """ 755 cmd = u""" 756 update dem.lnk_identity2ext_id set 757 fk_origin = (select dem.add_external_id_type(%(type)s, %(issuer)s)), 758 external_id = %(value)s, 759 comment = %(comment)s 760 where id = %(pk)s""" 761 args = {'pk': pk_id, 'value': value, 'type': type, 'issuer': issuer, 'comment': comment} 762 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
763 #--------------------------------------------------------
764 - def get_external_ids(self, id_type=None, issuer=None):
765 where_parts = ['pk_identity = %(pat)s'] 766 args = {'pat': self.ID} 767 768 if id_type is not None: 769 where_parts.append(u'name = %(name)s') 770 args['name'] = id_type.strip() 771 772 if issuer is not None: 773 where_parts.append(u'issuer = %(issuer)s') 774 args['issuer'] = issuer.strip() 775 776 cmd = u"select * from dem.v_external_ids4identity where %s" % ' and '.join(where_parts) 777 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 778 779 return rows
780 #--------------------------------------------------------
781 - def delete_external_id(self, pk_ext_id=None):
782 cmd = u""" 783 delete from dem.lnk_identity2ext_id 784 where id_identity = %(pat)s and id = %(pk)s""" 785 args = {'pat': self.ID, 'pk': pk_ext_id} 786 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
787 #--------------------------------------------------------
788 - def assimilate_identity(self, other_identity=None, link_obj=None):
789 """Merge another identity into this one. 790 791 Keep this one. Delete other one.""" 792 793 if other_identity.ID == self.ID: 794 return True, None 795 796 curr_pat = gmCurrentPatient() 797 if curr_pat.connected: 798 if other_identity.ID == curr_pat.ID: 799 return False, _('Cannot merge active patient into another patient.') 800 801 queries = [] 802 args = {'old_pat': other_identity.ID, 'new_pat': self.ID} 803 804 # delete old allergy state 805 queries.append ({ 806 '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)', 807 'args': args 808 }) 809 # FIXME: adjust allergy_state in kept patient 810 811 # deactivate all names of old patient 812 queries.append ({ 813 'cmd': u'update dem.names set active = False where id_identity = %(old_pat)s', 814 'args': args 815 }) 816 817 # find FKs pointing to identity 818 FKs = gmPG2.get_foreign_keys2column ( 819 schema = u'dem', 820 table = u'identity', 821 column = u'pk' 822 ) 823 824 # generate UPDATEs 825 cmd_template = u'update %s set %s = %%(new_pat)s where %s = %%(old_pat)s' 826 for FK in FKs: 827 queries.append ({ 828 'cmd': cmd_template % (FK['referencing_table'], FK['referencing_column'], FK['referencing_column']), 829 'args': args 830 }) 831 832 # remove old identity entry 833 queries.append ({ 834 'cmd': u'delete from dem.identity where pk = %(old_pat)s', 835 'args': args 836 }) 837 838 _log.warning('identity [%s] is about to assimilate identity [%s]', self.ID, other_identity.ID) 839 840 gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, end_tx = True) 841 842 self.add_external_id ( 843 type_name = u'merged GNUmed identity primary key', 844 value = u'GNUmed::pk::%s' % other_identity.ID, 845 issuer = u'GNUmed' 846 ) 847 848 return True, None
849 #-------------------------------------------------------- 850 #--------------------------------------------------------
851 - def put_on_waiting_list(self, urgency=0, comment=None, zone=None):
852 cmd = u""" 853 insert into clin.waiting_list (fk_patient, urgency, comment, area, list_position) 854 values ( 855 %(pat)s, 856 %(urg)s, 857 %(cmt)s, 858 %(area)s, 859 (select coalesce((max(list_position) + 1), 1) from clin.waiting_list) 860 )""" 861 args = {'pat': self.ID, 'urg': urgency, 'cmt': comment, 'area': zone} 862 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], verbose=True)
863 #--------------------------------------------------------
864 - def export_as_gdt(self, filename=None, encoding='iso-8859-15', external_id_type=None):
865 866 template = u'%s%s%s\r\n' 867 868 file = codecs.open ( 869 filename = filename, 870 mode = 'wb', 871 encoding = encoding, 872 errors = 'strict' 873 ) 874 875 file.write(template % (u'013', u'8000', u'6301')) 876 file.write(template % (u'013', u'9218', u'2.10')) 877 if external_id_type is None: 878 file.write(template % (u'%03d' % (9 + len(str(self.ID))), u'3000', self.ID)) 879 else: 880 ext_ids = self.get_external_ids(id_type = external_id_type) 881 if len(ext_ids) > 0: 882 file.write(template % (u'%03d' % (9 + len(ext_ids[0]['value'])), u'3000', ext_ids[0]['value'])) 883 file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['lastnames']])), u'3101', self._payload[self._idx['lastnames']])) 884 file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['firstnames']])), u'3102', self._payload[self._idx['firstnames']])) 885 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'))) 886 file.write(template % (u'010', u'3110', gmXdtMappings.map_gender_gm2xdt[self._payload[self._idx['gender']]])) 887 file.write(template % (u'025', u'6330', 'GNUmed::9206::encoding')) 888 file.write(template % (u'%03d' % (9 + len(encoding)), u'6331', encoding)) 889 if external_id_type is None: 890 file.write(template % (u'029', u'6332', u'GNUmed::3000::source')) 891 file.write(template % (u'017', u'6333', u'internal')) 892 else: 893 if len(ext_ids) > 0: 894 file.write(template % (u'029', u'6332', u'GNUmed::3000::source')) 895 file.write(template % (u'%03d' % (9 + len(external_id_type)), u'6333', external_id_type)) 896 897 file.close()
898 #-------------------------------------------------------- 899 # occupations API 900 #--------------------------------------------------------
901 - def get_occupations(self):
902 cmd = u"select * from dem.v_person_jobs where pk_identity=%s" 903 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}]) 904 return rows
905 #-------------------------------------------------------- 942 #-------------------------------------------------------- 950 #-------------------------------------------------------- 951 # comms API 952 #--------------------------------------------------------
953 - def get_comm_channels(self, comm_medium=None):
954 cmd = u"select * from dem.v_person_comms where pk_identity = %s" 955 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx = True) 956 957 filtered = rows 958 959 if comm_medium is not None: 960 filtered = [] 961 for row in rows: 962 if row['comm_type'] == comm_medium: 963 filtered.append(row) 964 965 return [ gmDemographicRecord.cCommChannel(row = { 966 'pk_field': 'pk_lnk_identity2comm', 967 'data': r, 968 'idx': idx 969 }) for r in filtered 970 ]
971 #-------------------------------------------------------- 989 #-------------------------------------------------------- 995 #-------------------------------------------------------- 996 # contacts API 997 #--------------------------------------------------------
998 - def get_addresses(self, address_type=None):
999 cmd = u"select * from dem.v_pat_addresses where pk_identity=%s" 1000 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx=True) 1001 addresses = [] 1002 for r in rows: 1003 addresses.append(gmDemographicRecord.cPatientAddress(row={'idx': idx, 'data': r, 'pk_field': 'pk_address'})) 1004 1005 filtered = addresses 1006 1007 if address_type is not None: 1008 filtered = [] 1009 for adr in addresses: 1010 if adr['address_type'] == address_type: 1011 filtered.append(adr) 1012 1013 return filtered
1014 #-------------------------------------------------------- 1062 #---------------------------------------------------------------------- 1072 #---------------------------------------------------------------------- 1073 # relatives API 1074 #----------------------------------------------------------------------
1075 - def get_relatives(self):
1076 cmd = u""" 1077 select 1078 t.description, 1079 vbp.pk_identity as id, 1080 title, 1081 firstnames, 1082 lastnames, 1083 dob, 1084 cob, 1085 gender, 1086 karyotype, 1087 pupic, 1088 pk_marital_status, 1089 marital_status, 1090 xmin_identity, 1091 preferred 1092 from 1093 dem.v_basic_person vbp, dem.relation_types t, dem.lnk_person2relative l 1094 where 1095 ( 1096 l.id_identity = %(pk)s and 1097 vbp.pk_identity = l.id_relative and 1098 t.id = l.id_relation_type 1099 ) or ( 1100 l.id_relative = %(pk)s and 1101 vbp.pk_identity = l.id_identity and 1102 t.inverse = l.id_relation_type 1103 )""" 1104 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 1105 if len(rows) == 0: 1106 return [] 1107 return [(row[0], cIdentity(row = {'data': row[1:], 'idx':idx, 'pk_field': 'pk'})) for row in rows]
1108 #-------------------------------------------------------- 1128 #----------------------------------------------------------------------
1129 - def delete_relative(self, relation):
1130 # unlink only, don't delete relative itself 1131 self.set_relative(None, relation)
1132 #--------------------------------------------------------
1134 if self._payload[self._idx['pk_emergency_contact']] is None: 1135 return None 1136 return cIdentity(aPK_obj = self._payload[self._idx['pk_emergency_contact']])
1137 1138 emergency_contact_in_database = property(_get_emergency_contact_from_database, lambda x:x) 1139 #---------------------------------------------------------------------- 1140 # age/dob related 1141 #----------------------------------------------------------------------
1142 - def get_medical_age(self):
1143 dob = self['dob'] 1144 1145 if dob is None: 1146 return u'??' 1147 1148 if self['deceased'] is None: 1149 # return gmDateTime.format_interval_medically ( 1150 # pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone) - dob 1151 # ) 1152 return gmDateTime.format_apparent_age_medically ( 1153 age = gmDateTime.calculate_apparent_age(start = dob) 1154 ) 1155 1156 return u'%s%s' % ( 1157 gmTools.u_latin_cross, 1158 # gmDateTime.format_interval_medically(self['deceased'] - dob) 1159 gmDateTime.format_apparent_age_medically ( 1160 age = gmDateTime.calculate_apparent_age ( 1161 start = dob, 1162 end = self['deceased'] 1163 ) 1164 ) 1165 )
1166 #----------------------------------------------------------------------
1167 - def dob_in_range(self, min_distance=u'1 week', max_distance=u'1 week'):
1168 cmd = u'select dem.dob_is_in_range(%(dob)s, %(min)s, %(max)s)' 1169 rows, idx = gmPG2.run_ro_queries ( 1170 queries = [{ 1171 'cmd': cmd, 1172 'args': {'dob': self['dob'], 'min': min_distance, 'max': max_distance} 1173 }] 1174 ) 1175 return rows[0][0]
1176 #---------------------------------------------------------------------- 1177 # practice related 1178 #----------------------------------------------------------------------
1179 - def get_last_encounter(self):
1180 cmd = u'select * from clin.v_most_recent_encounters where pk_patient=%s' 1181 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self._payload[self._idx['pk_identity']]]}]) 1182 if len(rows) > 0: 1183 return rows[0] 1184 else: 1185 return None
1186 #--------------------------------------------------------
1187 - def _get_messages(self):
1188 return gmProviderInbox.get_inbox_messages(pk_patient = self._payload[self._idx['pk_identity']])
1189
1190 - def _set_messages(self, messages):
1191 return
1192 1193 messages = property(_get_messages, _set_messages) 1194 #--------------------------------------------------------
1195 - def delete_message(self, pk=None):
1196 return gmProviderInbox.delete_inbox_message(inbox_message = pk)
1197 #--------------------------------------------------------
1198 - def _get_primary_provider(self):
1199 if self._payload[self._idx['pk_primary_provider']] is None: 1200 return None 1201 return cStaff(aPK_obj = self._payload[self._idx['pk_primary_provider']])
1202 1203 primary_provider = property(_get_primary_provider, lambda x:x) 1204 #---------------------------------------------------------------------- 1205 # convenience 1206 #----------------------------------------------------------------------
1207 - def get_dirname(self):
1208 """Format patient demographics into patient specific path name fragment.""" 1209 return '%s-%s%s-%s' % ( 1210 self._payload[self._idx['lastnames']].replace(u' ', u'_'), 1211 self._payload[self._idx['firstnames']].replace(u' ', u'_'), 1212 gmTools.coalesce(self._payload[self._idx['preferred']], u'', template_initial = u'-(%s)'), 1213 self.get_formatted_dob(format = '%Y-%m-%d', encoding = gmI18N.get_encoding()) 1214 )
1215 #============================================================
1216 -class cStaffMember(cIdentity):
1217 """Represents a staff member which is a person. 1218 1219 - a specializing subclass of cIdentity turning it into a staff member 1220 """
1221 - def __init__(self, identity = None):
1222 cIdentity.__init__(self, identity=identity) 1223 self.__db_cache = {}
1224 #--------------------------------------------------------
1225 - def get_inbox(self):
1226 return gmProviderInbox.cProviderInbox(provider_id = self.ID)
1227 #============================================================
1228 -class cPatient(cIdentity):
1229 """Represents a person which is a patient. 1230 1231 - a specializing subclass of cIdentity turning it into a patient 1232 - its use is to cache subobjects like EMR and document folder 1233 """
1234 - def __init__(self, aPK_obj=None, row=None):
1235 cIdentity.__init__(self, aPK_obj=aPK_obj, row=row) 1236 self.__db_cache = {} 1237 self.__emr_access_lock = threading.Lock()
1238 #--------------------------------------------------------
1239 - def cleanup(self):
1240 """Do cleanups before dying. 1241 1242 - note that this may be called in a thread 1243 """ 1244 if self.__db_cache.has_key('clinical record'): 1245 self.__db_cache['clinical record'].cleanup() 1246 if self.__db_cache.has_key('document folder'): 1247 self.__db_cache['document folder'].cleanup() 1248 cIdentity.cleanup(self)
1249 #----------------------------------------------------------
1250 - def get_emr(self):
1251 if not self.__emr_access_lock.acquire(False): 1252 raise AttributeError('cannot access EMR') 1253 try: 1254 emr = self.__db_cache['clinical record'] 1255 self.__emr_access_lock.release() 1256 return emr 1257 except KeyError: 1258 pass 1259 1260 self.__db_cache['clinical record'] = gmClinicalRecord.cClinicalRecord(aPKey = self._payload[self._idx['pk_identity']]) 1261 self.__emr_access_lock.release() 1262 return self.__db_cache['clinical record']
1263 #--------------------------------------------------------
1264 - def get_document_folder(self):
1265 try: 1266 return self.__db_cache['document folder'] 1267 except KeyError: 1268 pass 1269 1270 self.__db_cache['document folder'] = cDocumentFolder(aPKey = self._payload[self._idx['pk_identity']]) 1271 return self.__db_cache['document folder']
1272 #============================================================
1273 -class gmCurrentPatient(gmBorg.cBorg):
1274 """Patient Borg to hold currently active patient. 1275 1276 There may be many instances of this but they all share state. 1277 """
1278 - def __init__(self, patient=None, forced_reload=False):
1279 """Change or get currently active patient. 1280 1281 patient: 1282 * None: get currently active patient 1283 * -1: unset currently active patient 1284 * cPatient instance: set active patient if possible 1285 """ 1286 # make sure we do have a patient pointer 1287 try: 1288 tmp = self.patient 1289 except AttributeError: 1290 self.patient = gmNull.cNull() 1291 self.__register_interests() 1292 # set initial lock state, 1293 # this lock protects against activating another patient 1294 # when we are controlled from a remote application 1295 self.__lock_depth = 0 1296 # initialize callback state 1297 self.__pre_selection_callbacks = [] 1298 1299 # user wants copy of current patient 1300 if patient is None: 1301 return None 1302 1303 # do nothing if patient is locked 1304 if self.locked: 1305 _log.error('patient [%s] is locked, cannot change to [%s]' % (self.patient['pk_identity'], patient)) 1306 return None 1307 1308 # user wants to explicitly unset current patient 1309 if patient == -1: 1310 _log.debug('explicitly unsetting current patient') 1311 if not self.__run_pre_selection_callbacks(): 1312 _log.debug('not unsetting current patient') 1313 return None 1314 self.__send_pre_selection_notification() 1315 self.patient.cleanup() 1316 self.patient = gmNull.cNull() 1317 self.__send_selection_notification() 1318 return None 1319 1320 # must be cPatient instance, then 1321 if not isinstance(patient, cPatient): 1322 _log.error('cannot set active patient to [%s], must be either None, -1 or cPatient instance' % str(patient)) 1323 raise TypeError, 'gmPerson.gmCurrentPatient.__init__(): <patient> must be None, -1 or cPatient instance but is: %s' % str(patient) 1324 1325 # same ID, no change needed 1326 if (self.patient['pk_identity'] == patient['pk_identity']) and not forced_reload: 1327 return None 1328 1329 # user wants different patient 1330 _log.debug('patient change [%s] -> [%s] requested', self.patient['pk_identity'], patient['pk_identity']) 1331 1332 # everything seems swell 1333 if not self.__run_pre_selection_callbacks(): 1334 _log.debug('not changing current patient') 1335 return None 1336 self.__send_pre_selection_notification() 1337 self.patient.cleanup() 1338 self.patient = patient 1339 self.patient.get_emr() 1340 self.__send_selection_notification() 1341 1342 return None
1343 #--------------------------------------------------------
1344 - def __register_interests(self):
1345 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_identity_change) 1346 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_identity_change)
1347 #--------------------------------------------------------
1348 - def _on_identity_change(self):
1349 """Listen for patient *data* change.""" 1350 self.patient.refetch_payload()
1351 #-------------------------------------------------------- 1352 # external API 1353 #--------------------------------------------------------
1354 - def register_pre_selection_callback(self, callback=None):
1355 if not callable(callback): 1356 raise TypeError(u'callback [%s] not callable' % callback) 1357 1358 self.__pre_selection_callbacks.append(callback)
1359 #--------------------------------------------------------
1360 - def _get_connected(self):
1361 return (not isinstance(self.patient, gmNull.cNull))
1362
1363 - def _set_connected(self):
1364 raise AttributeError(u'invalid to set <connected> state')
1365 1366 connected = property(_get_connected, _set_connected) 1367 #--------------------------------------------------------
1368 - def _get_locked(self):
1369 return (self.__lock_depth > 0)
1370
1371 - def _set_locked(self, locked):
1372 if locked: 1373 self.__lock_depth = self.__lock_depth + 1 1374 gmDispatcher.send(signal='patient_locked') 1375 else: 1376 if self.__lock_depth == 0: 1377 _log.error('lock/unlock imbalance, trying to refcount lock depth below 0') 1378 return 1379 else: 1380 self.__lock_depth = self.__lock_depth - 1 1381 gmDispatcher.send(signal='patient_unlocked')
1382 1383 locked = property(_get_locked, _set_locked) 1384 #--------------------------------------------------------
1385 - def force_unlock(self):
1386 _log.info('forced patient unlock at lock depth [%s]' % self.__lock_depth) 1387 self.__lock_depth = 0 1388 gmDispatcher.send(signal='patient_unlocked')
1389 #-------------------------------------------------------- 1390 # patient change handling 1391 #--------------------------------------------------------
1393 if isinstance(self.patient, gmNull.cNull): 1394 return True 1395 1396 for call_back in self.__pre_selection_callbacks: 1397 try: 1398 successful = call_back() 1399 except: 1400 _log.exception('callback [%s] failed', call_back) 1401 print "*** pre-selection callback failed ***" 1402 print type(call_back) 1403 print call_back 1404 return False 1405 1406 if not successful: 1407 _log.debug('callback [%s] returned False', call_back) 1408 return False 1409 1410 return True
1411 #--------------------------------------------------------
1413 """Sends signal when another patient is about to become active. 1414 1415 This does NOT wait for signal handlers to complete. 1416 """ 1417 kwargs = { 1418 'signal': u'pre_patient_selection', 1419 'sender': id(self.__class__), 1420 'pk_identity': self.patient['pk_identity'] 1421 } 1422 gmDispatcher.send(**kwargs)
1423 #--------------------------------------------------------
1425 """Sends signal when another patient has actually been made active.""" 1426 kwargs = { 1427 'signal': u'post_patient_selection', 1428 'sender': id(self.__class__), 1429 'pk_identity': self.patient['pk_identity'] 1430 } 1431 gmDispatcher.send(**kwargs)
1432 #-------------------------------------------------------- 1433 # __getattr__ handling 1434 #--------------------------------------------------------
1435 - def __getattr__(self, attribute):
1436 if attribute == 'patient': 1437 raise AttributeError 1438 if not isinstance(self.patient, gmNull.cNull): 1439 return getattr(self.patient, attribute)
1440 #-------------------------------------------------------- 1441 # __get/setitem__ handling 1442 #--------------------------------------------------------
1443 - def __getitem__(self, attribute = None):
1444 """Return any attribute if known how to retrieve it by proxy. 1445 """ 1446 return self.patient[attribute]
1447 #--------------------------------------------------------
1448 - def __setitem__(self, attribute, value):
1449 self.patient[attribute] = value
1450 #============================================================ 1451 # match providers 1452 #============================================================
1453 -class cMatchProvider_Provider(gmMatchProvider.cMatchProvider_SQL2):
1454 - def __init__(self):
1455 gmMatchProvider.cMatchProvider_SQL2.__init__( 1456 self, 1457 queries = [ 1458 u"""select 1459 pk_staff, 1460 short_alias || ' (' || coalesce(title, '') || firstnames || ' ' || lastnames || ')', 1461 1 1462 from dem.v_staff 1463 where 1464 is_active and ( 1465 short_alias %(fragment_condition)s or 1466 firstnames %(fragment_condition)s or 1467 lastnames %(fragment_condition)s or 1468 db_user %(fragment_condition)s 1469 )""" 1470 ] 1471 ) 1472 self.setThresholds(1, 2, 3)
1473 #============================================================ 1474 # convenience functions 1475 #============================================================
1476 -def create_name(pk_person, firstnames, lastnames, active=False):
1477 queries = [{ 1478 'cmd': u"select dem.add_name(%s, %s, %s, %s)", 1479 'args': [pk_person, firstnames, lastnames, active] 1480 }] 1481 rows, idx = gmPG2.run_rw_queries(queries=queries, return_data=True) 1482 name = cPersonName(aPK_obj = rows[0][0]) 1483 return name
1484 #============================================================
1485 -def create_identity(gender=None, dob=None, lastnames=None, firstnames=None):
1486 1487 cmd1 = u"""INSERT INTO dem.identity (gender, dob) VALUES (%s, %s)""" 1488 cmd2 = u""" 1489 INSERT INTO dem.names ( 1490 id_identity, lastnames, firstnames 1491 ) VALUES ( 1492 currval('dem.identity_pk_seq'), coalesce(%s, 'xxxDEFAULTxxx'), coalesce(%s, 'xxxDEFAULTxxx') 1493 ) RETURNING id_identity""" 1494 rows, idx = gmPG2.run_rw_queries ( 1495 queries = [ 1496 {'cmd': cmd1, 'args': [gender, dob]}, 1497 {'cmd': cmd2, 'args': [lastnames, firstnames]} 1498 ], 1499 return_data = True 1500 ) 1501 ident = cIdentity(aPK_obj=rows[0][0]) 1502 gmHooks.run_hook_script(hook = u'post_person_creation') 1503 return ident
1504 #============================================================
1505 -def create_dummy_identity():
1506 cmd = u"INSERT INTO dem.identity(gender) VALUES ('xxxDEFAULTxxx') RETURNING pk" 1507 rows, idx = gmPG2.run_rw_queries ( 1508 queries = [{'cmd': cmd}], 1509 return_data = True 1510 ) 1511 return gmDemographicRecord.cIdentity(aPK_obj = rows[0][0])
1512 #============================================================
1513 -def set_active_patient(patient=None, forced_reload=False):
1514 """Set active patient. 1515 1516 If patient is -1 the active patient will be UNset. 1517 """ 1518 if isinstance(patient, cPatient): 1519 pat = patient 1520 elif isinstance(patient, cIdentity): 1521 pat = cPatient(aPK_obj=patient['pk_identity']) 1522 elif isinstance(patient, cStaff): 1523 pat = cPatient(aPK_obj=patient['pk_identity']) 1524 elif isinstance(patient, gmCurrentPatient): 1525 pat = patient.patient 1526 elif patient == -1: 1527 pat = patient 1528 else: 1529 raise ValueError('<patient> must be either -1, cPatient, cStaff, cIdentity or gmCurrentPatient instance, is: %s' % patient) 1530 1531 # attempt to switch 1532 try: 1533 gmCurrentPatient(patient = pat, forced_reload = forced_reload) 1534 except: 1535 _log.exception('error changing active patient to [%s]' % patient) 1536 return False 1537 1538 return True
1539 #============================================================ 1540 # gender related 1541 #------------------------------------------------------------
1542 -def get_gender_list():
1543 """Retrieves the list of known genders from the database.""" 1544 global __gender_idx 1545 global __gender_list 1546 1547 if __gender_list is None: 1548 cmd = u"select tag, l10n_tag, label, l10n_label, sort_weight from dem.v_gender_labels order by sort_weight desc" 1549 __gender_list, __gender_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 1550 1551 return (__gender_list, __gender_idx)
1552 #------------------------------------------------------------ 1553 map_gender2mf = { 1554 'm': u'm', 1555 'f': u'f', 1556 'tf': u'f', 1557 'tm': u'm', 1558 'h': u'mf' 1559 } 1560 #------------------------------------------------------------ 1561 # Maps GNUmed related i18n-aware gender specifiers to a unicode symbol. 1562 map_gender2symbol = { 1563 'm': u'\u2642', 1564 'f': u'\u2640', 1565 'tf': u'\u26A5\u2640', 1566 'tm': u'\u26A5\u2642', 1567 'h': u'\u26A5' 1568 # 'tf': u'\u2642\u2640-\u2640', 1569 # 'tm': u'\u2642\u2640-\u2642', 1570 # 'h': u'\u2642\u2640' 1571 } 1572 #------------------------------------------------------------
1573 -def map_gender2salutation(gender=None):
1574 """Maps GNUmed related i18n-aware gender specifiers to a human-readable salutation.""" 1575 1576 global __gender2salutation_map 1577 1578 if __gender2salutation_map is None: 1579 genders, idx = get_gender_list() 1580 __gender2salutation_map = { 1581 'm': _('Mr'), 1582 'f': _('Mrs'), 1583 'tf': u'', 1584 'tm': u'', 1585 'h': u'' 1586 } 1587 for g in genders: 1588 __gender2salutation_map[g[idx['l10n_tag']]] = __gender2salutation_map[g[idx['tag']]] 1589 __gender2salutation_map[g[idx['label']]] = __gender2salutation_map[g[idx['tag']]] 1590 __gender2salutation_map[g[idx['l10n_label']]] = __gender2salutation_map[g[idx['tag']]] 1591 1592 return __gender2salutation_map[gender]
1593 #------------------------------------------------------------
1594 -def map_firstnames2gender(firstnames=None):
1595 """Try getting the gender for the given first name.""" 1596 1597 if firstnames is None: 1598 return None 1599 1600 rows, idx = gmPG2.run_ro_queries(queries = [{ 1601 'cmd': u"select gender from dem.name_gender_map where name ilike %(fn)s limit 1", 1602 'args': {'fn': firstnames} 1603 }]) 1604 1605 if len(rows) == 0: 1606 return None 1607 1608 return rows[0][0]
1609 #============================================================
1610 -def get_staff_list(active_only=False):
1611 if active_only: 1612 cmd = u"select * from dem.v_staff where is_active order by can_login desc, short_alias asc" 1613 else: 1614 cmd = u"select * from dem.v_staff order by can_login desc, is_active desc, short_alias asc" 1615 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True) 1616 staff_list = [] 1617 for row in rows: 1618 obj_row = { 1619 'idx': idx, 1620 'data': row, 1621 'pk_field': 'pk_staff' 1622 } 1623 staff_list.append(cStaff(row=obj_row)) 1624 return staff_list
1625 #============================================================
1626 -def get_persons_from_pks(pks=None):
1627 return [ cIdentity(aPK_obj = pk) for pk in pks ]
1628 #============================================================
1629 -def get_person_from_xdt(filename=None, encoding=None, dob_format=None):
1630 from Gnumed.business import gmXdtObjects 1631 return gmXdtObjects.read_person_from_xdt(filename=filename, encoding=encoding, dob_format=dob_format)
1632 #============================================================
1633 -def get_persons_from_pracsoft_file(filename=None, encoding='ascii'):
1634 from Gnumed.business import gmPracSoftAU 1635 return gmPracSoftAU.read_persons_from_pracsoft_file(filename=filename, encoding=encoding)
1636 #============================================================ 1637 # main/testing 1638 #============================================================ 1639 if __name__ == '__main__': 1640 1641 if len(sys.argv) == 1: 1642 sys.exit() 1643 1644 if sys.argv[1] != 'test': 1645 sys.exit() 1646 1647 import datetime 1648 1649 gmI18N.activate_locale() 1650 gmI18N.install_domain() 1651 gmDateTime.init() 1652 1653 #--------------------------------------------------------
1654 - def test_set_active_pat():
1655 1656 ident = cIdentity(1) 1657 print "setting active patient with", ident 1658 set_active_patient(patient=ident) 1659 1660 patient = cPatient(12) 1661 print "setting active patient with", patient 1662 set_active_patient(patient=patient) 1663 1664 pat = gmCurrentPatient() 1665 print pat['dob'] 1666 #pat['dob'] = 'test' 1667 1668 staff = cStaff() 1669 print "setting active patient with", staff 1670 set_active_patient(patient=staff) 1671 1672 print "setting active patient with -1" 1673 set_active_patient(patient=-1)
1674 #--------------------------------------------------------
1675 - def test_dto_person():
1676 dto = cDTO_person() 1677 dto.firstnames = 'Sepp' 1678 dto.lastnames = 'Herberger' 1679 dto.gender = 'male' 1680 dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone) 1681 print dto 1682 1683 print dto['firstnames'] 1684 print dto['lastnames'] 1685 print dto['gender'] 1686 print dto['dob'] 1687 1688 for key in dto.keys(): 1689 print key
1690 #--------------------------------------------------------
1691 - def test_staff():
1692 staff = cStaff() 1693 print staff 1694 print staff.inbox 1695 print staff.inbox.messages
1696 #--------------------------------------------------------
1697 - def test_current_provider():
1698 staff = cStaff() 1699 provider = gmCurrentProvider(provider = staff) 1700 print provider 1701 print provider.inbox 1702 print provider.inbox.messages 1703 print provider.database_language 1704 tmp = provider.database_language 1705 provider.database_language = None 1706 print provider.database_language 1707 provider.database_language = tmp 1708 print provider.database_language
1709 #--------------------------------------------------------
1710 - def test_identity():
1711 # create patient 1712 print '\n\nCreating identity...' 1713 new_identity = create_identity(gender='m', dob='2005-01-01', lastnames='test lastnames', firstnames='test firstnames') 1714 print 'Identity created: %s' % new_identity 1715 1716 print '\nSetting title and gender...' 1717 new_identity['title'] = 'test title'; 1718 new_identity['gender'] = 'f'; 1719 new_identity.save_payload() 1720 print 'Refetching identity from db: %s' % cIdentity(aPK_obj=new_identity['pk_identity']) 1721 1722 print '\nGetting all names...' 1723 for a_name in new_identity.get_names(): 1724 print a_name 1725 print 'Active name: %s' % (new_identity.get_active_name()) 1726 print 'Setting nickname...' 1727 new_identity.set_nickname(nickname='test nickname') 1728 print 'Refetching all names...' 1729 for a_name in new_identity.get_names(): 1730 print a_name 1731 print 'Active name: %s' % (new_identity.get_active_name()) 1732 1733 print '\nIdentity occupations: %s' % new_identity['occupations'] 1734 print 'Creating identity occupation...' 1735 new_identity.link_occupation('test occupation') 1736 print 'Identity occupations: %s' % new_identity['occupations'] 1737 1738 print '\nIdentity addresses: %s' % new_identity.get_addresses() 1739 print 'Creating identity address...' 1740 # make sure the state exists in the backend 1741 new_identity.link_address ( 1742 number = 'test 1234', 1743 street = 'test street', 1744 postcode = 'test postcode', 1745 urb = 'test urb', 1746 state = 'SN', 1747 country = 'DE' 1748 ) 1749 print 'Identity addresses: %s' % new_identity.get_addresses() 1750 1751 print '\nIdentity communications: %s' % new_identity.get_comm_channels() 1752 print 'Creating identity communication...' 1753 new_identity.link_comm_channel('homephone', '1234566') 1754 print 'Identity communications: %s' % new_identity.get_comm_channels()
1755 #--------------------------------------------------------
1756 - def test_name():
1757 for pk in range(1,16): 1758 name = cPersonName(aPK_obj=pk) 1759 print name.description 1760 print ' ', name
1761 #-------------------------------------------------------- 1762 #test_dto_person() 1763 #test_identity() 1764 #test_set_active_pat() 1765 #test_search_by_dto() 1766 #test_staff() 1767 test_current_provider() 1768 #test_name() 1769 1770 #map_gender2salutation('m') 1771 # module functions 1772 #genders, idx = get_gender_list() 1773 #print "\n\nRetrieving gender enum (tag, label, weight):" 1774 #for gender in genders: 1775 # print "%s, %s, %s" % (gender[idx['tag']], gender[idx['l10n_label']], gender[idx['sort_weight']]) 1776 1777 #comms = get_comm_list() 1778 #print "\n\nRetrieving communication media enum (id, description): %s" % comms 1779 1780 #============================================================ 1781