Package Gnumed :: Package business :: Module gmPerson
[frames] | no frames]

Source Code for Module Gnumed.business.gmPerson

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