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