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