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