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