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