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