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

Source Code for Module Gnumed.business.gmPerson

   1  # -*- coding: utf-8 -*- 
   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 io 
  18  import threading 
  19  import logging 
  20  import io 
  21  import inspect 
  22  from xml.etree import ElementTree as etree 
  23   
  24   
  25  # GNUmed 
  26  if __name__ == '__main__': 
  27          logging.basicConfig(level = logging.DEBUG) 
  28          sys.path.insert(0, '../../') 
  29  from Gnumed.pycommon import gmExceptions 
  30  from Gnumed.pycommon import gmDispatcher 
  31  from Gnumed.pycommon import gmBorg 
  32  from Gnumed.pycommon import gmI18N 
  33  if __name__ == '__main__': 
  34          gmI18N.activate_locale() 
  35          gmI18N.install_domain() 
  36  from Gnumed.pycommon import gmNull 
  37  from Gnumed.pycommon import gmBusinessDBObject 
  38  from Gnumed.pycommon import gmTools 
  39  from Gnumed.pycommon import gmPG2 
  40  from Gnumed.pycommon import gmDateTime 
  41  from Gnumed.pycommon import gmMatchProvider 
  42  from Gnumed.pycommon import gmLog2 
  43  from Gnumed.pycommon import gmHooks 
  44   
  45  from Gnumed.business import gmDemographicRecord 
  46  from Gnumed.business import gmClinicalRecord 
  47  from Gnumed.business import gmXdtMappings 
  48  from Gnumed.business import gmProviderInbox 
  49  from Gnumed.business import gmExportArea 
  50  from Gnumed.business import gmBilling 
  51  from Gnumed.business import gmAutoHints 
  52  from Gnumed.business.gmDocuments import cDocumentFolder 
  53   
  54   
  55  _log = logging.getLogger('gm.person') 
  56   
  57  __gender_list = None 
  58  __gender_idx = None 
  59   
  60  __gender2salutation_map = None 
  61  __gender2string_map = None 
  62   
  63  #============================================================ 
  64  _MERGE_SCRIPT_HEADER = """-- GNUmed patient merge script 
  65  -- created: %(date)s 
  66  -- patient to keep : #%(pat2keep)s 
  67  -- patient to merge: #%(pat2del)s 
  68  -- 
  69  -- You can EASILY cause mangled data by uncritically applying this script, so ... 
  70  -- ... BE POSITIVELY SURE YOU UNDERSTAND THE FULL EXTENT OF WHAT IT DOES ! 
  71   
  72   
  73  --set default_transaction_read_only to off; 
  74   
  75  BEGIN; 
  76  """ 
  77   
  78  #============================================================ 
79 -def external_id_exists(pk_issuer, value):
80 cmd = 'SELECT COUNT(1) FROM dem.lnk_identity2ext_id WHERE fk_origin = %(issuer)s AND external_id = %(val)s' 81 args = {'issuer': pk_issuer, 'val': value} 82 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 83 return rows[0][0]
84 85 #============================================================
86 -def get_potential_person_dupes(lastnames, dob, firstnames=None, active_only=True):
87 args = { 88 'last': lastnames, 89 'dob': dob 90 } 91 where_parts = [ 92 "lastnames = %(last)s", 93 "dem.date_trunc_utc('day', dob) = dem.date_trunc_utc('day', %(dob)s)" 94 ] 95 if firstnames is not None: 96 if firstnames.strip() != '': 97 #where_parts.append(u"position(%(first)s in firstnames) = 1") 98 where_parts.append("firstnames ~* %(first)s") 99 args['first'] = '\\m' + firstnames 100 if active_only: 101 cmd = """SELECT COUNT(1) FROM dem.v_active_persons WHERE %s""" % ' AND '.join(where_parts) 102 else: 103 cmd = """SELECT COUNT(1) FROM dem.v_all_persons WHERE %s""" % ' AND '.join(where_parts) 104 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 105 return rows[0][0]
106 107 #============================================================
108 -def this_person_exists(lastnames, firstnames, dob, comment):
109 # backend also looks at gender (IOW, only fails on same-gender dupes) 110 # implement in plpgsql and re-use in both validation trigger and here 111 if comment is not None: 112 comment = comment.strip() 113 if comment == u'': 114 comment = None 115 args = { 116 'last': lastnames.strip(), 117 'first': firstnames.strip(), 118 'dob': dob, 119 'cmt': comment 120 } 121 where_parts = [ 122 u'lower(lastnames) = lower(%(last)s)', 123 u'lower(firstnames) = lower(%(first)s)', 124 u"dem.date_trunc_utc('day', dob) IS NOT DISTINCT FROM dem.date_trunc_utc('day', %(dob)s)", 125 u'lower(comment) IS NOT DISTINCT FROM lower(%(cmt)s)' 126 ] 127 cmd = u"SELECT COUNT(1) FROM dem.v_persons WHERE %s" % u' AND '.join(where_parts) 128 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 129 return rows[0][0]
130 131 #============================================================ 132 # FIXME: make this work as a mapping type, too
133 -class cDTO_person(object):
134
135 - def __init__(self):
136 self.identity = None 137 self.external_ids = [] 138 self.comm_channels = [] 139 self.addresses = [] 140 141 self.firstnames = None 142 self.lastnames = None 143 self.title = None 144 self.gender = None 145 self.dob = None 146 self.dob_is_estimated = False 147 self.source = self.__class__.__name__
148 #-------------------------------------------------------- 149 # external API 150 #--------------------------------------------------------
151 - def keys(self):
152 return 'firstnames lastnames dob gender title'.split()
153 #--------------------------------------------------------
154 - def delete_from_source(self):
155 pass
156 #--------------------------------------------------------
157 - def is_unique(self):
158 where_snippets = [ 159 'firstnames = %(first)s', 160 'lastnames = %(last)s' 161 ] 162 args = { 163 'first': self.firstnames, 164 'last': self.lastnames 165 } 166 if self.dob is not None: 167 where_snippets.append("dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %(dob)s)") 168 args['dob'] = self.dob.replace(hour = 23, minute = 59, second = 59) 169 if self.gender is not None: 170 where_snippets.append('gender = %(sex)s') 171 args['sex'] = self.gender 172 cmd = 'SELECT count(1) FROM dem.v_person_names WHERE %s' % ' AND '.join(where_snippets) 173 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 174 175 return rows[0][0] == 1
176 177 is_unique = property(is_unique, lambda x:x) 178 #--------------------------------------------------------
179 - def exists(self):
180 where_snippets = [ 181 'firstnames = %(first)s', 182 'lastnames = %(last)s' 183 ] 184 args = { 185 'first': self.firstnames, 186 'last': self.lastnames 187 } 188 if self.dob is not None: 189 where_snippets.append("dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %(dob)s)") 190 args['dob'] = self.dob.replace(hour = 23, minute = 59, second = 59) 191 if self.gender is not None: 192 where_snippets.append('gender = %(sex)s') 193 args['sex'] = self.gender 194 cmd = 'SELECT count(1) FROM dem.v_person_names WHERE %s' % ' AND '.join(where_snippets) 195 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 196 197 return rows[0][0] > 0
198 199 exists = property(exists, lambda x:x) 200 #--------------------------------------------------------
201 - def get_candidate_identities(self, can_create=False):
202 """Generate generic queries. 203 204 - not locale dependant 205 - data -> firstnames, lastnames, dob, gender 206 207 shall we mogrify name parts ? probably not as external 208 sources should know what they do 209 210 finds by inactive name, too, but then shows 211 the corresponding active name ;-) 212 213 Returns list of matching identities (may be empty) 214 or None if it was told to create an identity but couldn't. 215 """ 216 where_snippets = [] 217 args = {} 218 219 where_snippets.append('lower(firstnames) = lower(%(first)s)') 220 args['first'] = self.firstnames 221 222 where_snippets.append('lower(lastnames) = lower(%(last)s)') 223 args['last'] = self.lastnames 224 225 if self.dob is not None: 226 where_snippets.append("dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %(dob)s)") 227 args['dob'] = self.dob.replace(hour = 23, minute = 59, second = 59) 228 229 if self.gender is not None: 230 where_snippets.append('lower(gender) = lower(%(sex)s)') 231 args['sex'] = self.gender 232 233 # FIXME: allow disabled persons ? 234 cmd = """ 235 SELECT *, '%s' AS match_type 236 FROM dem.v_active_persons 237 WHERE 238 pk_identity IN ( 239 SELECT pk_identity FROM dem.v_person_names WHERE %s 240 ) 241 ORDER BY lastnames, firstnames, dob""" % ( 242 _('external patient source (name, gender, date of birth)'), 243 ' AND '.join(where_snippets) 244 ) 245 246 try: 247 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx=True) 248 except: 249 _log.error('cannot get candidate identities for dto "%s"' % self) 250 _log.exception('query %s' % cmd) 251 rows = [] 252 253 if len(rows) == 0: 254 _log.debug('no candidate identity matches found') 255 if not can_create: 256 return [] 257 ident = self.import_into_database() 258 if ident is None: 259 return None 260 identities = [ident] 261 else: 262 identities = [ cPerson(row = {'pk_field': 'pk_identity', 'data': row, 'idx': idx}) for row in rows ] 263 264 return identities
265 #--------------------------------------------------------
266 - def import_into_database(self):
267 """Imports self into the database.""" 268 269 self.identity = create_identity ( 270 firstnames = self.firstnames, 271 lastnames = self.lastnames, 272 gender = self.gender, 273 dob = self.dob 274 ) 275 276 if self.identity is None: 277 return None 278 279 if self.dob_is_estimated: 280 self.identity['dob_is_estimated'] = True 281 if self.title is not None: 282 self.identity['title'] = self.title 283 self.identity.save() 284 285 for ext_id in self.external_ids: 286 try: 287 self.identity.add_external_id ( 288 type_name = ext_id['name'], 289 value = ext_id['value'], 290 issuer = ext_id['issuer'], 291 comment = ext_id['comment'] 292 ) 293 except Exception: 294 _log.exception('cannot import <external ID> from external data source') 295 gmLog2.log_stack_trace() 296 297 for comm in self.comm_channels: 298 try: 299 self.identity.link_comm_channel ( 300 comm_medium = comm['channel'], 301 url = comm['url'] 302 ) 303 except Exception: 304 _log.exception('cannot import <comm channel> from external data source') 305 gmLog2.log_stack_trace() 306 307 for adr in self.addresses: 308 try: 309 self.identity.link_address ( 310 adr_type = adr['type'], 311 number = adr['number'], 312 subunit = adr['subunit'], 313 street = adr['street'], 314 postcode = adr['zip'], 315 urb = adr['urb'], 316 region_code = adr['region_code'], 317 country_code = adr['country_code'] 318 ) 319 except Exception: 320 _log.exception('cannot import <address> from external data source') 321 gmLog2.log_stack_trace() 322 323 return self.identity
324 #--------------------------------------------------------
325 - def import_extra_data(self, *args, **kwargs):
326 pass
327 #--------------------------------------------------------
328 - def remember_external_id(self, name=None, value=None, issuer=None, comment=None):
329 value = value.strip() 330 if value == '': 331 return 332 name = name.strip() 333 if name == '': 334 raise ValueError(_('<name> cannot be empty')) 335 issuer = issuer.strip() 336 if issuer == '': 337 raise ValueError(_('<issuer> cannot be empty')) 338 self.external_ids.append({'name': name, 'value': value, 'issuer': issuer, 'comment': comment})
339 #--------------------------------------------------------
340 - def remember_comm_channel(self, channel=None, url=None):
341 url = url.strip() 342 if url == '': 343 return 344 channel = channel.strip() 345 if channel == '': 346 raise ValueError(_('<channel> cannot be empty')) 347 self.comm_channels.append({'channel': channel, 'url': url})
348 #--------------------------------------------------------
349 - def remember_address(self, number=None, street=None, urb=None, region_code=None, zip=None, country_code=None, adr_type=None, subunit=None):
350 number = number.strip() 351 if number == '': 352 raise ValueError(_('<number> cannot be empty')) 353 street = street.strip() 354 if street == '': 355 raise ValueError(_('<street> cannot be empty')) 356 urb = urb.strip() 357 if urb == '': 358 raise ValueError(_('<urb> cannot be empty')) 359 zip = zip.strip() 360 if zip == '': 361 raise ValueError(_('<zip> cannot be empty')) 362 country_code = country_code.strip() 363 if country_code == '': 364 raise ValueError(_('<country_code> cannot be empty')) 365 if region_code is not None: 366 region_code = region_code.strip() 367 if region_code in [None, '']: 368 region_code = '??' 369 self.addresses.append ({ 370 'type': adr_type, 371 'number': number, 372 'subunit': subunit, 373 'street': street, 374 'zip': zip, 375 'urb': urb, 376 'region_code': region_code, 377 'country_code': country_code 378 })
379 #-------------------------------------------------------- 380 # customizing behaviour 381 #--------------------------------------------------------
382 - def __str__(self):
383 return '<%s (%s) @ %s: %s %s (%s) %s>' % ( 384 self.__class__.__name__, 385 self.source, 386 id(self), 387 self.firstnames, 388 self.lastnames, 389 self.gender, 390 self.dob 391 )
392 #--------------------------------------------------------
393 - def __setattr__(self, attr, val):
394 """Do some sanity checks on self.* access.""" 395 396 if attr == 'gender': 397 if val is None: 398 object.__setattr__(self, attr, val) 399 return 400 glist, idx = get_gender_list() 401 for gender in glist: 402 if str(val) in [gender[0], gender[1], gender[2], gender[3]]: 403 val = gender[idx['tag']] 404 object.__setattr__(self, attr, val) 405 return 406 raise ValueError('invalid gender: [%s]' % val) 407 408 if attr == 'dob': 409 if val is not None: 410 if not isinstance(val, pyDT.datetime): 411 raise TypeError('invalid type for DOB (must be datetime.datetime): %s [%s]' % (type(val), val)) 412 if val.tzinfo is None: 413 raise ValueError('datetime.datetime instance is lacking a time zone: [%s]' % val.isoformat()) 414 415 object.__setattr__(self, attr, val) 416 return
417 #--------------------------------------------------------
418 - def __getitem__(self, attr):
419 return getattr(self, attr)
420 421 #============================================================
422 -class cPersonName(gmBusinessDBObject.cBusinessDBObject):
423 _cmd_fetch_payload = "SELECT * FROM dem.v_person_names WHERE pk_name = %s" 424 _cmds_store_payload = [ 425 """UPDATE dem.names SET 426 active = FALSE 427 WHERE 428 %(active_name)s IS TRUE -- act only when needed and only 429 AND 430 id_identity = %(pk_identity)s -- on names of this identity 431 AND 432 active IS TRUE -- which are active 433 AND 434 id != %(pk_name)s -- but NOT *this* name 435 """, 436 """update dem.names set 437 active = %(active_name)s, 438 preferred = %(preferred)s, 439 comment = %(comment)s 440 where 441 id = %(pk_name)s and 442 id_identity = %(pk_identity)s and -- belt and suspenders 443 xmin = %(xmin_name)s""", 444 """select xmin as xmin_name from dem.names where id = %(pk_name)s""" 445 ] 446 _updatable_fields = ['active_name', 'preferred', 'comment'] 447 #--------------------------------------------------------
448 - def __setitem__(self, attribute, value):
449 if attribute == 'active_name': 450 # cannot *directly* deactivate a name, only indirectly 451 # by activating another one 452 # FIXME: should be done at DB level 453 if self._payload[self._idx['active_name']] is True: 454 return 455 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
456 #--------------------------------------------------------
457 - def _get_description(self):
458 return '%(last)s, %(title)s %(first)s%(nick)s' % { 459 'last': self._payload[self._idx['lastnames']], 460 'title': gmTools.coalesce ( 461 self._payload[self._idx['title']], 462 map_gender2salutation(self._payload[self._idx['gender']]) 463 ), 464 'first': self._payload[self._idx['firstnames']], 465 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], '', " '%s'", '%s') 466 }
467 468 description = property(_get_description, lambda x:x)
469 470 #============================================================ 471 _SQL_get_active_person = "SELECT * FROM dem.v_active_persons WHERE pk_identity = %s" 472 _SQL_get_any_person = "SELECT * FROM dem.v_all_persons WHERE pk_identity = %s" 473
474 -class cPerson(gmBusinessDBObject.cBusinessDBObject):
475 _cmd_fetch_payload = _SQL_get_any_person 476 _cmds_store_payload = [ 477 """UPDATE dem.identity SET 478 gender = %(gender)s, 479 dob = %(dob)s, 480 dob_is_estimated = %(dob_is_estimated)s, 481 tob = %(tob)s, 482 title = gm.nullify_empty_string(%(title)s), 483 fk_marital_status = %(pk_marital_status)s, 484 deceased = %(deceased)s, 485 emergency_contact = gm.nullify_empty_string(%(emergency_contact)s), 486 fk_emergency_contact = %(pk_emergency_contact)s, 487 fk_primary_provider = %(pk_primary_provider)s, 488 comment = gm.nullify_empty_string(%(comment)s) 489 WHERE 490 pk = %(pk_identity)s and 491 xmin = %(xmin_identity)s 492 RETURNING 493 xmin AS xmin_identity""" 494 ] 495 _updatable_fields = [ 496 "title", 497 "dob", 498 "tob", 499 "gender", 500 "pk_marital_status", 501 'deceased', 502 'emergency_contact', 503 'pk_emergency_contact', 504 'pk_primary_provider', 505 'comment', 506 'dob_is_estimated' 507 ] 508 #--------------------------------------------------------
509 - def _get_ID(self):
510 return self._payload[self._idx['pk_identity']]
511 - def _set_ID(self, value):
512 raise AttributeError('setting ID of identity is not allowed')
513 514 ID = property(_get_ID, _set_ID) 515 516 #--------------------------------------------------------
517 - def __setitem__(self, attribute, value):
518 519 if attribute == 'dob': 520 if value is not None: 521 522 if isinstance(value, pyDT.datetime): 523 if value.tzinfo is None: 524 raise ValueError('datetime.datetime instance is lacking a time zone: [%s]' % dt.isoformat()) 525 else: 526 raise TypeError('[%s]: type [%s] (%s) invalid for attribute [dob], must be datetime.datetime or None' % (self.__class__.__name__, type(value), value)) 527 528 # compare DOB at seconds level 529 if self._payload[self._idx['dob']] is not None: 530 old_dob = gmDateTime.pydt_strftime ( 531 self._payload[self._idx['dob']], 532 format = '%Y %m %d %H %M %S', 533 accuracy = gmDateTime.acc_seconds 534 ) 535 new_dob = gmDateTime.pydt_strftime ( 536 value, 537 format = '%Y %m %d %H %M %S', 538 accuracy = gmDateTime.acc_seconds 539 ) 540 if new_dob == old_dob: 541 return 542 543 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
544 545 #--------------------------------------------------------
546 - def cleanup(self):
547 pass
548 549 #--------------------------------------------------------
550 - def _get_is_patient(self):
551 return identity_is_patient(self._payload[self._idx['pk_identity']])
552
553 - def _set_is_patient(self, turn_into_patient):
554 if turn_into_patient: 555 return turn_identity_into_patient(self._payload[self._idx['pk_identity']]) 556 return False
557 558 is_patient = property(_get_is_patient, _set_is_patient) 559 560 #--------------------------------------------------------
561 - def _get_as_patient(self):
562 return cPatient(self._payload[self._idx['pk_identity']])
563 564 as_patient = property(_get_as_patient, lambda x:x) 565 566 #--------------------------------------------------------
567 - def _get_staff_id(self):
568 cmd = "SELECT pk FROM dem.staff WHERE fk_identity = %(pk)s" 569 args = {'pk': self._payload[self._idx['pk_identity']]} 570 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 571 if len(rows) == 0: 572 return None 573 return rows[0][0]
574 575 staff_id = property(_get_staff_id, lambda x:x) 576 577 #-------------------------------------------------------- 578 # identity API 579 #--------------------------------------------------------
580 - def _get_gender_symbol(self):
581 return map_gender2symbol[self._payload[self._idx['gender']]]
582 583 gender_symbol = property(_get_gender_symbol, lambda x:x) 584 #--------------------------------------------------------
585 - def _get_gender_string(self):
586 return map_gender2string(gender = self._payload[self._idx['gender']])
587 588 gender_string = property(_get_gender_string, lambda x:x) 589 #--------------------------------------------------------
590 - def _get_gender_list(self):
591 gender_list, tmp = get_gender_list() 592 return gender_list
593 594 gender_list = property(_get_gender_list, lambda x:x) 595 #--------------------------------------------------------
596 - def get_active_name(self):
597 names = self.get_names(active_only = True) 598 if len(names) == 0: 599 _log.error('cannot retrieve active name for patient [%s]', self._payload[self._idx['pk_identity']]) 600 return None 601 return names[0]
602 603 active_name = property(get_active_name, lambda x:x) 604 #--------------------------------------------------------
605 - def get_names(self, active_only=False, exclude_active=False):
606 607 args = {'pk_pat': self._payload[self._idx['pk_identity']]} 608 where_parts = ['pk_identity = %(pk_pat)s'] 609 if active_only: 610 where_parts.append('active_name is True') 611 if exclude_active: 612 where_parts.append('active_name is False') 613 cmd = """ 614 SELECT * 615 FROM dem.v_person_names 616 WHERE %s 617 ORDER BY active_name DESC, lastnames, firstnames 618 """ % ' AND '.join(where_parts) 619 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 620 621 if len(rows) == 0: 622 # no names registered for patient 623 return [] 624 625 names = [ cPersonName(row = {'idx': idx, 'data': r, 'pk_field': 'pk_name'}) for r in rows ] 626 return names
627 #--------------------------------------------------------
628 - def get_description_gender(self, with_nickname=True):
629 if with_nickname: 630 template = _('%(last)s,%(title)s %(first)s%(nick)s (%(sex)s)') 631 else: 632 template = _('%(last)s,%(title)s %(first)s (%(sex)s)') 633 return template % { 634 'last': self._payload[self._idx['lastnames']], 635 'title': gmTools.coalesce(self._payload[self._idx['title']], '', ' %s'), 636 'first': self._payload[self._idx['firstnames']], 637 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], '', " '%s'"), 638 'sex': self.gender_symbol 639 }
640 641 #--------------------------------------------------------
642 - def get_description(self, with_nickname=True):
643 if with_nickname: 644 template = _('%(last)s,%(title)s %(first)s%(nick)s') 645 else: 646 template = _('%(last)s,%(title)s %(first)s') 647 return template % { 648 'last': self._payload[self._idx['lastnames']], 649 'title': gmTools.coalesce(self._payload[self._idx['title']], '', ' %s'), 650 'first': self._payload[self._idx['firstnames']], 651 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], '', " '%s'") 652 }
653 654 #--------------------------------------------------------
655 - def add_name(self, firstnames, lastnames, active=True):
656 """Add a name. 657 658 @param firstnames The first names. 659 @param lastnames The last names. 660 @param active When True, the new name will become the active one (hence setting other names to inactive) 661 @type active A bool instance 662 """ 663 name = create_name(self.ID, firstnames, lastnames, active) 664 if active: 665 self.refetch_payload() 666 return name
667 668 #--------------------------------------------------------
669 - def delete_name(self, name=None):
670 cmd = "delete from dem.names where id = %(name)s and id_identity = %(pat)s" 671 args = {'name': name['pk_name'], 'pat': self.ID} 672 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
673 # can't have been the active name as that would raise an 674 # exception (since no active name would be left) so no 675 # data refetch needed 676 677 #--------------------------------------------------------
678 - def set_nickname(self, nickname=None):
679 """ 680 Set the nickname. Setting the nickname only makes sense for the currently 681 active name. 682 @param nickname The preferred/nick/warrior name to set. 683 """ 684 if self._payload[self._idx['preferred']] == nickname: 685 return True 686 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': "SELECT dem.set_nickname(%s, %s)", 'args': [self.ID, nickname]}]) 687 # setting nickname doesn't change dem.identity, so other fields 688 # of dem.v_active_persons do not get changed as a consequence of 689 # setting the nickname, hence locally setting nickname matches 690 # in-database reality 691 self._payload[self._idx['preferred']] = nickname 692 #self.refetch_payload() 693 return True
694 695 #--------------------------------------------------------
696 - def get_tags(self, order_by=None):
697 if order_by is None: 698 order_by = '' 699 else: 700 order_by = 'ORDER BY %s' % order_by 701 702 cmd = gmDemographicRecord._SQL_get_person_tags % ('pk_identity = %%(pat)s %s' % order_by) 703 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.ID}}], get_col_idx = True) 704 705 return [ gmDemographicRecord.cPersonTag(row = {'data': r, 'idx': idx, 'pk_field': 'pk_identity_tag'}) for r in rows ]
706 707 tags = property(get_tags, lambda x:x) 708 709 #--------------------------------------------------------
710 - def add_tag(self, tag):
711 args = { 712 'tag': tag, 713 'identity': self.ID 714 } 715 716 # already exists ? 717 cmd = "SELECT pk FROM dem.identity_tag WHERE fk_tag = %(tag)s AND fk_identity = %(identity)s" 718 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 719 if len(rows) > 0: 720 return gmDemographicRecord.cPersonTag(aPK_obj = rows[0]['pk']) 721 722 # no, add 723 cmd = """ 724 INSERT INTO dem.identity_tag ( 725 fk_tag, 726 fk_identity 727 ) VALUES ( 728 %(tag)s, 729 %(identity)s 730 ) 731 RETURNING pk 732 """ 733 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False) 734 return gmDemographicRecord.cPersonTag(aPK_obj = rows[0]['pk'])
735 736 #--------------------------------------------------------
737 - def remove_tag(self, tag):
738 cmd = "DELETE FROM dem.identity_tag WHERE pk = %(pk)s" 739 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': tag}}])
740 741 #-------------------------------------------------------- 742 # external ID API 743 # 744 # since external IDs are not treated as first class 745 # citizens (classes in their own right, that is), we 746 # handle them *entirely* within cPerson, also they 747 # only make sense with one single person (like names) 748 # and are not reused (like addresses), so they are 749 # truly added/deleted, not just linked/unlinked 750 #--------------------------------------------------------
751 - def add_external_id(self, type_name=None, value=None, issuer=None, comment=None, pk_type=None):
752 """Adds an external ID to the patient. 753 754 creates ID type if necessary 755 """ 756 # check for existing ID 757 if pk_type is not None: 758 cmd = """ 759 select * from dem.v_external_ids4identity where 760 pk_identity = %(pat)s and 761 pk_type = %(pk_type)s and 762 value = %(val)s""" 763 else: 764 # by type/value/issuer 765 if issuer is None: 766 cmd = """ 767 select * from dem.v_external_ids4identity where 768 pk_identity = %(pat)s and 769 name = %(name)s and 770 value = %(val)s""" 771 else: 772 cmd = """ 773 select * from dem.v_external_ids4identity where 774 pk_identity = %(pat)s and 775 name = %(name)s and 776 value = %(val)s and 777 issuer = %(issuer)s""" 778 args = { 779 'pat': self.ID, 780 'name': type_name, 781 'val': value, 782 'issuer': issuer, 783 'pk_type': pk_type 784 } 785 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 786 787 # create new ID if not found 788 if len(rows) == 0: 789 790 args = { 791 'pat': self.ID, 792 'val': value, 793 'type_name': type_name, 794 'pk_type': pk_type, 795 'issuer': issuer, 796 'comment': comment 797 } 798 799 if pk_type is None: 800 cmd = """insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values ( 801 %(val)s, 802 (select dem.add_external_id_type(%(type_name)s, %(issuer)s)), 803 %(comment)s, 804 %(pat)s 805 )""" 806 else: 807 cmd = """insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values ( 808 %(val)s, 809 %(pk_type)s, 810 %(comment)s, 811 %(pat)s 812 )""" 813 814 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 815 816 # or update comment of existing ID 817 else: 818 row = rows[0] 819 if comment is not None: 820 # comment not already there ? 821 if gmTools.coalesce(row['comment'], '').find(comment.strip()) == -1: 822 comment = '%s%s' % (gmTools.coalesce(row['comment'], '', '%s // '), comment.strip) 823 cmd = "update dem.lnk_identity2ext_id set comment = %(comment)s where id=%(pk)s" 824 args = {'comment': comment, 'pk': row['pk_id']} 825 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
826 827 #--------------------------------------------------------
828 - def update_external_id(self, pk_id=None, type=None, value=None, issuer=None, comment=None):
829 """Edits an existing external ID. 830 831 Creates ID type if necessary. 832 """ 833 cmd = """ 834 UPDATE dem.lnk_identity2ext_id SET 835 fk_origin = (SELECT dem.add_external_id_type(%(type)s, %(issuer)s)), 836 external_id = %(value)s, 837 comment = gm.nullify_empty_string(%(comment)s) 838 WHERE 839 id = %(pk)s 840 """ 841 args = {'pk': pk_id, 'value': value, 'type': type, 'issuer': issuer, 'comment': comment} 842 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
843 844 #--------------------------------------------------------
845 - def get_external_ids(self, id_type=None, issuer=None):
846 where_parts = ['pk_identity = %(pat)s'] 847 args = {'pat': self.ID} 848 849 if id_type is not None: 850 where_parts.append('name = %(name)s') 851 args['name'] = id_type.strip() 852 853 if issuer is not None: 854 where_parts.append('issuer = %(issuer)s') 855 args['issuer'] = issuer.strip() 856 857 cmd = "SELECT * FROM dem.v_external_ids4identity WHERE %s" % ' AND '.join(where_parts) 858 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 859 860 return rows
861 862 external_ids = property(get_external_ids, lambda x:x) 863 864 #--------------------------------------------------------
865 - def delete_external_id(self, pk_ext_id=None):
866 cmd = """ 867 DELETE FROM dem.lnk_identity2ext_id 868 WHERE id_identity = %(pat)s AND id = %(pk)s""" 869 args = {'pat': self.ID, 'pk': pk_ext_id} 870 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
871 872 #--------------------------------------------------------
873 - def suggest_external_id(self, target=None, encoding=None):
874 name = self.active_name 875 last = ' '.join(p for p in name['lastnames'].split("-")) 876 last = ' '.join(p for p in last.split(".")) 877 last = ' '.join(p for p in last.split("'")) 878 last = ''.join(gmTools.capitalize(text = p, mode = gmTools.CAPS_FIRST_ONLY) for p in last.split(' ')) 879 first = ' '.join(p for p in name['firstnames'].split("-")) 880 first = ' '.join(p for p in first.split(".")) 881 first = ' '.join(p for p in first.split("'")) 882 first = ''.join(gmTools.capitalize(text = p, mode = gmTools.CAPS_FIRST_ONLY) for p in first.split(' ')) 883 suggestion = 'GMd-%s%s%s%s%s' % ( 884 gmTools.coalesce(target, '', '%s-'), 885 last, 886 first, 887 self.get_formatted_dob(format = '-%Y%m%d', none_string = ''), 888 gmTools.coalesce(self['gender'], '', '-%s') 889 ) 890 try: 891 import unidecode 892 return unidecode.unidecode(suggestion) 893 except ImportError: 894 _log.debug('cannot transliterate external ID suggestion, <unidecode> module not installed') 895 if encoding is None: 896 return suggestion 897 return suggestion.encode(encoding)
898 899 external_id_suggestion = property(suggest_external_id, lambda x:x) 900 901 #--------------------------------------------------------
902 - def suggest_external_ids(self, target=None, encoding=None):
903 names2use = [self.active_name] 904 names2use.extend(self.get_names(active_only = False, exclude_active = True)) 905 target = gmTools.coalesce(target, '', '%s-') 906 dob = self.get_formatted_dob(format = '-%Y%m%d', none_string = '') 907 gender = gmTools.coalesce(self['gender'], '', '-%s') 908 suggestions = [] 909 for name in names2use: 910 last = ' '.join(p for p in name['lastnames'].split("-")) 911 last = ' '.join(p for p in last.split(".")) 912 last = ' '.join(p for p in last.split("'")) 913 last = ''.join(gmTools.capitalize(text = p, mode = gmTools.CAPS_FIRST_ONLY) for p in last.split(' ')) 914 first = ' '.join(p for p in name['firstnames'].split("-")) 915 first = ' '.join(p for p in first.split(".")) 916 first = ' '.join(p for p in first.split("'")) 917 first = ''.join(gmTools.capitalize(text = p, mode = gmTools.CAPS_FIRST_ONLY) for p in first.split(' ')) 918 suggestion = 'GMd-%s%s%s%s%s' % (target, last, first, dob, gender) 919 try: 920 import unidecode 921 suggestions.append(unidecode.unidecode(suggestion)) 922 continue 923 except ImportError: 924 _log.debug('cannot transliterate external ID suggestion, <unidecode> module not installed') 925 if encoding is None: 926 suggestions.append(suggestion) 927 else: 928 suggestions.append(suggestion.encode(encoding)) 929 return suggestions
930 931 #-------------------------------------------------------- 932 #--------------------------------------------------------
933 - def assimilate_identity(self, other_identity=None, link_obj=None):
934 """Merge another identity into this one. 935 936 Keep this one. Delete other one.""" 937 938 if other_identity.ID == self.ID: 939 return True, None 940 941 curr_pat = gmCurrentPatient() 942 if curr_pat.connected: 943 if other_identity.ID == curr_pat.ID: 944 return False, _('Cannot merge active patient into another patient.') 945 946 now_here = gmDateTime.pydt_strftime(gmDateTime.pydt_now_here()) 947 distinguisher = _('merge of #%s into #%s @ %s') % (other_identity.ID, self.ID, now_here) 948 949 queries = [] 950 args = {'pat2del': other_identity.ID, 'pat2keep': self.ID} 951 952 # merge allergy state 953 queries.append ({ 954 'cmd': """ 955 UPDATE clin.allergy_state SET 956 has_allergy = greatest ( 957 (SELECT has_allergy FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2del)s), 958 (SELECT has_allergy FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2keep)s) 959 ), 960 -- perhaps use least() to play it safe and make it appear longer ago than it might have been, actually ? 961 last_confirmed = greatest ( 962 (SELECT last_confirmed FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2del)s), 963 (SELECT last_confirmed FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2keep)s) 964 ) 965 WHERE 966 pk = (SELECT pk_allergy_state FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2keep)s) 967 """, 968 'args': args 969 }) 970 # delete old allergy state 971 queries.append ({ 972 'cmd': 'DELETE FROM clin.allergy_state WHERE pk = (SELECT pk_allergy_state FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2del)s)', 973 'args': args 974 }) 975 976 # merge patient proxy 977 queries.append ({ 978 'cmd': """ 979 UPDATE clin.patient SET 980 edc = coalesce ( 981 edc, 982 (SELECT edc FROM clin.patient WHERE fk_identity = %(pat2del)s) 983 ) 984 WHERE 985 fk_identity = %(pat2keep)s 986 """, 987 'args': args 988 }) 989 990 # transfer names 991 # 1) hard-disambiguate all inactive names in old patient 992 # (the active one will be disambiguated upon being moved) 993 queries.append ({ 994 'cmd': """ 995 UPDATE dem.names d_n1 SET 996 comment = coalesce ( 997 comment, '' 998 ) || coalesce ( 999 ' (from identity: "' || (SELECT comment FROM dem.identity WHERE pk = %%(pat2del)s) || '")', 1000 '' 1001 ) || ' (during: "%s")' 1002 WHERE 1003 d_n1.id_identity = %%(pat2del)s 1004 """ % distinguisher, 1005 'args': args 1006 }) 1007 # 2) move inactive ones (dupes are expected to have been eliminated in step 1 above) 1008 queries.append ({ 1009 'cmd': u""" 1010 UPDATE dem.names d_n SET 1011 id_identity = %(pat2keep)s, 1012 lastnames = lastnames || ' [' || random()::TEXT || ']' 1013 WHERE 1014 d_n.id_identity = %(pat2del)s 1015 AND 1016 d_n.active IS false 1017 """, 1018 'args': args 1019 }) 1020 # 3) copy active name into pat2keep as an inactive name, 1021 # because each identity MUST have at LEAST one name, 1022 # we can't simply UPDATE over to pat2keep 1023 # also, needs de-duplication or else it would conflict with 1024 # *itself* on pat2keep 1025 queries.append ({ 1026 'cmd': """ 1027 INSERT INTO dem.names ( 1028 id_identity, active, firstnames, preferred, comment, 1029 lastnames 1030 ) 1031 SELECT 1032 %(pat2keep)s, false, firstnames, preferred, comment, 1033 lastnames || ' [' || random()::text || ']' 1034 FROM dem.names d_n 1035 WHERE 1036 d_n.id_identity = %(pat2del)s 1037 AND 1038 d_n.active IS true 1039 """, 1040 'args': args 1041 }) 1042 1043 # disambiguate potential dupes 1044 # - same-url comm channels 1045 queries.append ({ 1046 'cmd': """ 1047 UPDATE dem.lnk_identity2comm 1048 SET url = url || ' (%s)' 1049 WHERE 1050 fk_identity = %%(pat2del)s 1051 AND 1052 EXISTS ( 1053 SELECT 1 FROM dem.lnk_identity2comm d_li2c 1054 WHERE d_li2c.fk_identity = %%(pat2keep)s AND d_li2c.url = url 1055 ) 1056 """ % distinguisher, 1057 'args': args 1058 }) 1059 # - same-value external IDs 1060 queries.append ({ 1061 'cmd': """ 1062 UPDATE dem.lnk_identity2ext_id 1063 SET external_id = external_id || ' (%s)' 1064 WHERE 1065 id_identity = %%(pat2del)s 1066 AND 1067 EXISTS ( 1068 SELECT 1 FROM dem.lnk_identity2ext_id d_li2e 1069 WHERE 1070 d_li2e.id_identity = %%(pat2keep)s 1071 AND 1072 d_li2e.external_id = external_id 1073 AND 1074 d_li2e.fk_origin = fk_origin 1075 ) 1076 """ % distinguisher, 1077 'args': args 1078 }) 1079 # - same addresses 1080 queries.append ({ 1081 'cmd': """ 1082 DELETE FROM dem.lnk_person_org_address 1083 WHERE 1084 id_identity = %(pat2del)s 1085 AND 1086 id_address IN ( 1087 SELECT id_address FROM dem.lnk_person_org_address d_lpoa 1088 WHERE d_lpoa.id_identity = %(pat2keep)s 1089 ) 1090 """, 1091 'args': args 1092 }) 1093 1094 # find FKs pointing to dem.identity.pk 1095 FKs = gmPG2.get_foreign_keys2column ( 1096 schema = 'dem', 1097 table = 'identity', 1098 column = 'pk' 1099 ) 1100 # find FKs pointing to clin.patient.fk_identity 1101 FKs.extend (gmPG2.get_foreign_keys2column ( 1102 schema = 'clin', 1103 table = 'patient', 1104 column = 'fk_identity' 1105 )) 1106 1107 # generate UPDATEs 1108 cmd_template = 'UPDATE %s SET %s = %%(pat2keep)s WHERE %s = %%(pat2del)s' 1109 for FK in FKs: 1110 if FK['referencing_table'] in ['dem.names', 'clin.patient']: 1111 continue 1112 queries.append ({ 1113 'cmd': cmd_template % (FK['referencing_table'], FK['referencing_column'], FK['referencing_column']), 1114 'args': args 1115 }) 1116 1117 # delete old patient proxy 1118 queries.append ({ 1119 'cmd': 'DELETE FROM clin.patient WHERE fk_identity = %(pat2del)s', 1120 'args': args 1121 }) 1122 1123 # remove old identity entry 1124 queries.append ({ 1125 'cmd': 'delete from dem.identity where pk = %(pat2del)s', 1126 'args': args 1127 }) 1128 1129 script_name = gmTools.get_unique_filename(prefix = 'gm-assimilate-%(pat2del)s-into-%(pat2keep)s-' % args, suffix = '.sql') 1130 _log.warning('identity [%s] is about to assimilate identity [%s], SQL script [%s]', self.ID, other_identity.ID, script_name) 1131 1132 script = io.open(script_name, 'wt') 1133 args['date'] = gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), '%Y %B %d %H:%M') 1134 script.write(_MERGE_SCRIPT_HEADER % args) 1135 for query in queries: 1136 script.write(query['cmd'] % args) 1137 script.write(';\n') 1138 script.write('\nROLLBACK;\n') 1139 script.write('--COMMIT;\n') 1140 script.close() 1141 1142 try: 1143 gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, end_tx = True) 1144 except Exception: 1145 return False, _('The merge failed. Check the log and [%s]') % script_name 1146 1147 self.add_external_id ( 1148 type_name = 'merged GNUmed identity primary key', 1149 value = 'GNUmed::pk::%s' % other_identity.ID, 1150 issuer = 'GNUmed' 1151 ) 1152 1153 return True, None
1154 1155 #-------------------------------------------------------- 1156 #--------------------------------------------------------
1157 - def put_on_waiting_list(self, urgency=0, comment=None, zone=None):
1158 cmd = """ 1159 insert into clin.waiting_list (fk_patient, urgency, comment, area, list_position) 1160 values ( 1161 %(pat)s, 1162 %(urg)s, 1163 %(cmt)s, 1164 %(area)s, 1165 (select coalesce((max(list_position) + 1), 1) from clin.waiting_list) 1166 )""" 1167 args = {'pat': self.ID, 'urg': urgency, 'cmt': comment, 'area': zone} 1168 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], verbose = True) 1169 gmHooks.run_hook_script(hook = 'after_waiting_list_modified')
1170 1171 #--------------------------------------------------------
1172 - def get_waiting_list_entry(self):
1173 cmd = """SELECT * FROM clin.v_waiting_list WHERE pk_identity = %(pat)s""" 1174 args = {'pat': self.ID} 1175 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1176 return rows
1177 1178 waiting_list_entries = property(get_waiting_list_entry, lambda x:x) 1179 1180 #--------------------------------------------------------
1181 - def _get_export_area(self):
1182 return gmExportArea.cExportArea(self.ID)
1183 1184 export_area = property(_get_export_area, lambda x:x) 1185 #--------------------------------------------------------
1186 - def export_as_gdt(self, filename=None, encoding='iso-8859-15', external_id_type=None):
1187 1188 template = '%s%s%s\r\n' 1189 1190 if filename is None: 1191 filename = gmTools.get_unique_filename ( 1192 prefix = 'gm-patient-', 1193 suffix = '.gdt' 1194 ) 1195 1196 gdt_file = io.open(filename, mode = 'wt', encoding = encoding, errors = 'strict') 1197 1198 gdt_file.write(template % ('013', '8000', '6301')) 1199 gdt_file.write(template % ('013', '9218', '2.10')) 1200 if external_id_type is None: 1201 gdt_file.write(template % ('%03d' % (9 + len(str(self.ID))), '3000', self.ID)) 1202 else: 1203 ext_ids = self.get_external_ids(id_type = external_id_type) 1204 if len(ext_ids) > 0: 1205 gdt_file.write(template % ('%03d' % (9 + len(ext_ids[0]['value'])), '3000', ext_ids[0]['value'])) 1206 gdt_file.write(template % ('%03d' % (9 + len(self._payload[self._idx['lastnames']])), '3101', self._payload[self._idx['lastnames']])) 1207 gdt_file.write(template % ('%03d' % (9 + len(self._payload[self._idx['firstnames']])), '3102', self._payload[self._idx['firstnames']])) 1208 gdt_file.write(template % ('%03d' % (9 + len(self._payload[self._idx['dob']].strftime('%d%m%Y'))), '3103', self._payload[self._idx['dob']].strftime('%d%m%Y'))) 1209 gdt_file.write(template % ('010', '3110', gmXdtMappings.map_gender_gm2xdt[self._payload[self._idx['gender']]])) 1210 gdt_file.write(template % ('025', '6330', 'GNUmed::9206::encoding')) 1211 gdt_file.write(template % ('%03d' % (9 + len(encoding)), '6331', encoding)) 1212 if external_id_type is None: 1213 gdt_file.write(template % ('029', '6332', 'GNUmed::3000::source')) 1214 gdt_file.write(template % ('017', '6333', 'internal')) 1215 else: 1216 if len(ext_ids) > 0: 1217 gdt_file.write(template % ('029', '6332', 'GNUmed::3000::source')) 1218 gdt_file.write(template % ('%03d' % (9 + len(external_id_type)), '6333', external_id_type)) 1219 1220 gdt_file.close() 1221 1222 return filename
1223 #--------------------------------------------------------
1224 - def export_as_xml_linuxmednews(self, filename=None):
1225 1226 if filename is None: 1227 filename = gmTools.get_unique_filename ( 1228 prefix = 'gm-LinuxMedNews_demographics-', 1229 suffix = '.xml' 1230 ) 1231 1232 dob_format = '%Y-%m-%d' 1233 pat = etree.Element('patient') 1234 1235 first = etree.SubElement(pat, 'firstname') 1236 first.text = gmTools.coalesce(self._payload[self._idx['firstnames']], '') 1237 1238 last = etree.SubElement(pat, 'lastname') 1239 last.text = gmTools.coalesce(self._payload[self._idx['lastnames']], '') 1240 1241 # privacy 1242 #middle = etree.SubElement(pat, u'middlename') 1243 #middle.set(u'comment', _('preferred name/call name/...')) 1244 #middle.text = gmTools.coalesce(self._payload[self._idx['preferred']], u'') 1245 1246 pref = etree.SubElement(pat, 'name_prefix') 1247 pref.text = gmTools.coalesce(self._payload[self._idx['title']], '') 1248 1249 suff = etree.SubElement(pat, 'name_suffix') 1250 suff.text = '' 1251 1252 dob = etree.SubElement(pat, 'DOB') 1253 dob.set('format', dob_format) 1254 dob.text = gmDateTime.pydt_strftime(self._payload[self._idx['dob']], dob_format, accuracy = gmDateTime.acc_days, none_str = '') 1255 1256 gender = etree.SubElement(pat, 'gender') 1257 gender.set('comment', self.gender_string) 1258 if self._payload[self._idx['gender']] is None: 1259 gender.text = '' 1260 else: 1261 gender.text = map_gender2mf[self._payload[self._idx['gender']]] 1262 1263 home = etree.SubElement(pat, 'home_address') 1264 adrs = self.get_addresses(address_type = 'home') 1265 if len(adrs) > 0: 1266 adr = adrs[0] 1267 city = etree.SubElement(home, 'city') 1268 city.set('comment', gmTools.coalesce(adr['suburb'], '')) 1269 city.text = gmTools.coalesce(adr['urb'], '') 1270 1271 region = etree.SubElement(home, 'region') 1272 region.set('comment', gmTools.coalesce(adr['l10n_region'], '')) 1273 region.text = gmTools.coalesce(adr['code_region'], '') 1274 1275 zipcode = etree.SubElement(home, 'postal_code') 1276 zipcode.text = gmTools.coalesce(adr['postcode'], '') 1277 1278 street = etree.SubElement(home, 'street') 1279 street.set('comment', gmTools.coalesce(adr['notes_street'], '')) 1280 street.text = gmTools.coalesce(adr['street'], '') 1281 1282 no = etree.SubElement(home, 'number') 1283 no.set('subunit', gmTools.coalesce(adr['subunit'], '')) 1284 no.set('comment', gmTools.coalesce(adr['notes_subunit'], '')) 1285 no.text = gmTools.coalesce(adr['number'], '') 1286 1287 country = etree.SubElement(home, 'country') 1288 country.set('comment', adr['l10n_country']) 1289 country.text = gmTools.coalesce(adr['code_country'], '') 1290 1291 phone = etree.SubElement(pat, 'home_phone') 1292 rec = self.get_comm_channels(comm_medium = 'homephone') 1293 if len(rec) > 0: 1294 if not rec[0]['is_confidential']: 1295 phone.set('comment', gmTools.coalesce(rec[0]['comment'], '')) 1296 phone.text = rec[0]['url'] 1297 1298 phone = etree.SubElement(pat, 'work_phone') 1299 rec = self.get_comm_channels(comm_medium = 'workphone') 1300 if len(rec) > 0: 1301 if not rec[0]['is_confidential']: 1302 phone.set('comment', gmTools.coalesce(rec[0]['comment'], '')) 1303 phone.text = rec[0]['url'] 1304 1305 phone = etree.SubElement(pat, 'cell_phone') 1306 rec = self.get_comm_channels(comm_medium = 'mobile') 1307 if len(rec) > 0: 1308 if not rec[0]['is_confidential']: 1309 phone.set('comment', gmTools.coalesce(rec[0]['comment'], '')) 1310 phone.text = rec[0]['url'] 1311 1312 tree = etree.ElementTree(pat) 1313 tree.write(filename, encoding = 'UTF-8') 1314 1315 return filename
1316 1317 #--------------------------------------------------------
1318 - def export_as_vcard(self, filename=None):
1319 # http://vobject.skyhouseconsulting.com/usage.html 1320 # http://en.wikipedia.org/wiki/VCard 1321 # http://svn.osafoundation.org/vobject/trunk/vobject/vcard.py 1322 # http://www.ietf.org/rfc/rfc2426.txt 1323 1324 dob_format = '%Y%m%d' 1325 1326 import vobject 1327 1328 vc = vobject.vCard() 1329 vc.add('kind') 1330 vc.kind.value = 'individual' 1331 1332 vc.add('fn') 1333 vc.fn.value = self.get_description() 1334 vc.add('n') 1335 vc.n.value = vobject.vcard.Name(family = self._payload[self._idx['lastnames']], given = self._payload[self._idx['firstnames']]) 1336 # privacy 1337 #vc.add(u'nickname') 1338 #vc.nickname.value = gmTools.coalesce(self._payload[self._idx['preferred']], u'') 1339 vc.add('title') 1340 vc.title.value = gmTools.coalesce(self._payload[self._idx['title']], '') 1341 vc.add('gender') 1342 # FIXME: dont know how to add gender_string after ';' 1343 vc.gender.value = map_gender2vcard[self._payload[self._idx['gender']]]#, self.gender_string 1344 vc.add('bday') 1345 vc.bday.value = gmDateTime.pydt_strftime(self._payload[self._idx['dob']], dob_format, accuracy = gmDateTime.acc_days, none_str = '') 1346 1347 channels = self.get_comm_channels(comm_medium = 'homephone') 1348 if len(channels) > 0: 1349 if not channels[0]['is_confidential']: 1350 vc.add('tel') 1351 vc.tel.value = channels[0]['url'] 1352 vc.tel.type_param = 'HOME' 1353 channels = self.get_comm_channels(comm_medium = 'workphone') 1354 if len(channels) > 0: 1355 if not channels[0]['is_confidential']: 1356 vc.add('tel') 1357 vc.tel.value = channels[0]['url'] 1358 vc.tel.type_param = 'WORK' 1359 channels = self.get_comm_channels(comm_medium = 'mobile') 1360 if len(channels) > 0: 1361 if not channels[0]['is_confidential']: 1362 vc.add('tel') 1363 vc.tel.value = channels[0]['url'] 1364 vc.tel.type_param = 'CELL' 1365 channels = self.get_comm_channels(comm_medium = 'fax') 1366 if len(channels) > 0: 1367 if not channels[0]['is_confidential']: 1368 vc.add('tel') 1369 vc.tel.value = channels[0]['url'] 1370 vc.tel.type_param = 'FAX' 1371 channels = self.get_comm_channels(comm_medium = 'email') 1372 if len(channels) > 0: 1373 if not channels[0]['is_confidential']: 1374 vc.add('email') 1375 vc.tel.value = channels[0]['url'] 1376 vc.tel.type_param = 'INTERNET' 1377 channels = self.get_comm_channels(comm_medium = 'web') 1378 if len(channels) > 0: 1379 if not channels[0]['is_confidential']: 1380 vc.add('url') 1381 vc.tel.value = channels[0]['url'] 1382 vc.tel.type_param = 'INTERNET' 1383 1384 adrs = self.get_addresses(address_type = 'home') 1385 if len(adrs) > 0: 1386 home_adr = adrs[0] 1387 vc.add('adr') 1388 vc.adr.type_param = 'HOME' 1389 vc.adr.value = vobject.vcard.Address() 1390 vc_adr = vc.adr.value 1391 vc_adr.extended = gmTools.coalesce(home_adr['subunit'], '') 1392 vc_adr.street = gmTools.coalesce(home_adr['street'], '', '%s ') + gmTools.coalesce(home_adr['number'], '') 1393 vc_adr.region = gmTools.coalesce(home_adr['l10n_region'], '') 1394 vc_adr.code = gmTools.coalesce(home_adr['postcode'], '') 1395 vc_adr.city = gmTools.coalesce(home_adr['urb'], '') 1396 vc_adr.country = gmTools.coalesce(home_adr['l10n_country'], '') 1397 1398 #photo (base64) 1399 1400 if filename is None: 1401 filename = gmTools.get_unique_filename ( 1402 prefix = 'gm-patient-', 1403 suffix = '.vcf' 1404 ) 1405 vcf = io.open(filename, mode = 'wt', encoding = 'utf8') 1406 try: 1407 vcf.write(vc.serialize().decode('utf-8')) 1408 except UnicodeDecodeError: 1409 _log.exception('failed to serialize VCF data') 1410 vcf.close() 1411 return 'cannot-serialize.vcf' 1412 vcf.close() 1413 1414 return filename
1415 #-------------------------------------------------------- 1416 # occupations API 1417 #--------------------------------------------------------
1418 - def get_occupations(self):
1419 return gmDemographicRecord.get_occupations(pk_identity = self.pk_obj)
1420 1421 #-------------------------------------------------------- 1458 #-------------------------------------------------------- 1466 #-------------------------------------------------------- 1467 # comms API 1468 #--------------------------------------------------------
1469 - def get_comm_channels(self, comm_medium=None):
1470 cmd = "select * from dem.v_person_comms where pk_identity = %s" 1471 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx = True) 1472 1473 filtered = rows 1474 1475 if comm_medium is not None: 1476 filtered = [] 1477 for row in rows: 1478 if row['comm_type'] == comm_medium: 1479 filtered.append(row) 1480 1481 return [ gmDemographicRecord.cCommChannel(row = { 1482 'pk_field': 'pk_lnk_identity2comm', 1483 'data': r, 1484 'idx': idx 1485 }) for r in filtered 1486 ]
1487 1488 comm_channels = property(get_comm_channels, lambda x:x) 1489 #-------------------------------------------------------- 1507 #-------------------------------------------------------- 1513 #-------------------------------------------------------- 1514 # contacts API 1515 #--------------------------------------------------------
1516 - def get_addresses(self, address_type=None):
1517 1518 cmd = "SELECT * FROM dem.v_pat_addresses WHERE pk_identity = %(pat)s" 1519 args = {'pat': self.pk_obj} 1520 if address_type is not None: 1521 cmd = cmd + " AND address_type = %(typ)s" 1522 args['typ'] = address_type 1523 1524 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1525 1526 return [ 1527 gmDemographicRecord.cPatientAddress(row = {'idx': idx, 'data': r, 'pk_field': 'pk_address'}) 1528 for r in rows 1529 ]
1530 #-------------------------------------------------------- 1576 #---------------------------------------------------------------------- 1597 #---------------------------------------------------------------------- 1598 # bills API 1599 #----------------------------------------------------------------------
1600 - def get_bills(self, order_by=None, pk_patient=None):
1601 return gmBilling.get_bills ( 1602 order_by = order_by, 1603 pk_patient = self.pk_obj 1604 )
1605 1606 bills = property(get_bills, lambda x:x) 1607 #---------------------------------------------------------------------- 1608 # relatives API 1609 #----------------------------------------------------------------------
1610 - def get_relatives(self):
1611 cmd = """ 1612 SELECT 1613 d_rt.description, 1614 d_vap.* 1615 FROM 1616 dem.v_all_persons d_vap, 1617 dem.relation_types d_rt, 1618 dem.lnk_person2relative d_lp2r 1619 WHERE 1620 ( d_lp2r.id_identity = %(pk)s 1621 AND 1622 d_vap.pk_identity = d_lp2r.id_relative 1623 AND 1624 d_rt.id = d_lp2r.id_relation_type 1625 ) or ( 1626 d_lp2r.id_relative = %(pk)s 1627 AND 1628 d_vap.pk_identity = d_lp2r.id_identity 1629 AND 1630 d_rt.inverse = d_lp2r.id_relation_type 1631 )""" 1632 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 1633 if len(rows) == 0: 1634 return [] 1635 return [(row[0], cPerson(row = {'data': row[1:], 'idx':idx, 'pk_field': 'pk_identity'})) for row in rows]
1636 #-------------------------------------------------------- 1656 #----------------------------------------------------------------------
1657 - def delete_relative(self, relation):
1658 # unlink only, don't delete relative itself 1659 self.set_relative(None, relation)
1660 #--------------------------------------------------------
1662 if self._payload[self._idx['pk_emergency_contact']] is None: 1663 return None 1664 return cPerson(self._payload[self._idx['pk_emergency_contact']])
1665 1666 emergency_contact_in_database = property(_get_emergency_contact_from_database, lambda x:x) 1667 1668 #---------------------------------------------------------------------- 1669 # age/dob related 1670 #----------------------------------------------------------------------
1671 - def get_formatted_dob(self, format='%Y %b %d', none_string=None, honor_estimation=False):
1672 return gmDateTime.format_dob ( 1673 self._payload[self._idx['dob']], 1674 format = format, 1675 none_string = none_string, 1676 dob_is_estimated = self._payload[self._idx['dob_is_estimated']] and honor_estimation 1677 )
1678 1679 #----------------------------------------------------------------------
1680 - def get_medical_age(self):
1681 dob = self['dob'] 1682 1683 if dob is None: 1684 return '??' 1685 1686 if dob > gmDateTime.pydt_now_here(): 1687 return _('invalid age: DOB in the future') 1688 1689 death = self['deceased'] 1690 1691 if death is None: 1692 return '%s%s' % ( 1693 gmTools.bool2subst ( 1694 self._payload[self._idx['dob_is_estimated']], 1695 gmTools.u_almost_equal_to, 1696 '' 1697 ), 1698 gmDateTime.format_apparent_age_medically ( 1699 age = gmDateTime.calculate_apparent_age(start = dob) 1700 ) 1701 ) 1702 1703 if dob > death: 1704 return _('invalid age: DOB after death') 1705 1706 return '%s%s%s' % ( 1707 gmTools.u_latin_cross, 1708 gmTools.bool2subst ( 1709 self._payload[self._idx['dob_is_estimated']], 1710 gmTools.u_almost_equal_to, 1711 '' 1712 ), 1713 gmDateTime.format_apparent_age_medically ( 1714 age = gmDateTime.calculate_apparent_age ( 1715 start = dob, 1716 end = self['deceased'] 1717 ) 1718 ) 1719 )
1720 1721 #----------------------------------------------------------------------
1722 - def dob_in_range(self, min_distance='1 week', max_distance='1 week'):
1723 if self['dob'] is None: 1724 return False 1725 cmd = 'select dem.dob_is_in_range(%(dob)s, %(min)s, %(max)s)' 1726 rows, idx = gmPG2.run_ro_queries ( 1727 queries = [{ 1728 'cmd': cmd, 1729 'args': {'dob': self['dob'], 'min': min_distance, 'max': max_distance} 1730 }] 1731 ) 1732 return rows[0][0]
1733 1734 #----------------------------------------------------------------------
1736 if self['dob'] is None: 1737 return None 1738 now = gmDateTime.pydt_now_here() 1739 if now.month < self['dob'].month: 1740 return False 1741 if now.month > self['dob'].month: 1742 return True 1743 # -> DOB is this month 1744 if now.day < self['dob'].day: 1745 return False 1746 if now.day > self['dob'].day: 1747 return True 1748 # -> DOB is today 1749 return False
1750 1751 current_birthday_passed = property(_get_current_birthday_passed) 1752 1753 #----------------------------------------------------------------------
1754 - def _get_birthday_this_year(self):
1755 if self['dob'] is None: 1756 return None 1757 now = gmDateTime.pydt_now_here() 1758 return gmDateTime.pydt_replace ( 1759 dt = self['dob'], 1760 year = now.year, 1761 strict = False 1762 )
1763 1764 birthday_this_year = property(_get_birthday_this_year) 1765 1766 #----------------------------------------------------------------------
1767 - def _get_birthday_next_year(self):
1768 if self['dob'] is None: 1769 return None 1770 now = gmDateTime.pydt_now_here() 1771 return gmDateTime.pydt_replace ( 1772 dt = self['dob'], 1773 year = now.year + 1, 1774 strict = False 1775 )
1776 1777 birthday_next_year = property(_get_birthday_next_year) 1778 1779 #----------------------------------------------------------------------
1780 - def _get_birthday_last_year(self):
1781 if self['dob'] is None: 1782 return None 1783 now = gmDateTime.pydt_now_here() 1784 return gmDateTime.pydt_replace ( 1785 dt = self['dob'], 1786 year = now.year - 1, 1787 strict = False 1788 )
1789 1790 birthday_last_year = property(_get_birthday_last_year, lambda x:x) 1791 1792 #---------------------------------------------------------------------- 1793 # practice related 1794 #----------------------------------------------------------------------
1795 - def get_last_encounter(self):
1796 cmd = 'select * from clin.v_most_recent_encounters where pk_patient=%s' 1797 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self._payload[self._idx['pk_identity']]]}]) 1798 if len(rows) > 0: 1799 return rows[0] 1800 else: 1801 return None
1802 #--------------------------------------------------------
1803 - def get_messages(self, order_by=None):
1804 return gmProviderInbox.get_inbox_messages(pk_patient = self._payload[self._idx['pk_identity']], order_by = order_by)
1805 1806 messages = property(get_messages, lambda x:x) 1807 #--------------------------------------------------------
1808 - def _get_overdue_messages(self):
1809 return gmProviderInbox.get_overdue_messages(pk_patient = self._payload[self._idx['pk_identity']])
1810 1811 overdue_messages = property(_get_overdue_messages, lambda x:x) 1812 1813 #--------------------------------------------------------
1814 - def delete_message(self, pk=None):
1815 return gmProviderInbox.delete_inbox_message(inbox_message = pk)
1816 1817 #--------------------------------------------------------
1818 - def _get_dynamic_hints(self, pk_encounter=None):
1819 return gmAutoHints.get_hints_for_patient ( 1820 pk_identity = self._payload[self._idx['pk_identity']], 1821 pk_encounter = pk_encounter 1822 )
1823 1824 dynamic_hints = property(_get_dynamic_hints, lambda x:x) 1825 1826 #--------------------------------------------------------
1827 - def _get_suppressed_hints(self):
1828 return gmAutoHints.get_suppressed_hints(pk_identity = self._payload[self._idx['pk_identity']])
1829 1830 suppressed_hints = property(_get_suppressed_hints, lambda x:x) 1831 1832 #--------------------------------------------------------
1834 if self._payload[self._idx['pk_primary_provider']] is None: 1835 return None 1836 cmd = "SELECT * FROM dem.v_all_persons WHERE pk_identity = (SELECT pk_identity FROM dem.v_staff WHERE pk_staff = %(pk_staff)s)" 1837 args = {'pk_staff': self._payload[self._idx['pk_primary_provider']]} 1838 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1839 if len(rows) == 0: 1840 return None 1841 return cPerson(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_identity'})
1842 1843 primary_provider_identity = property(_get_primary_provider_identity, lambda x:x) 1844 1845 #--------------------------------------------------------
1846 - def _get_primary_provider(self):
1847 if self._payload[self._idx['pk_primary_provider']] is None: 1848 return None 1849 from Gnumed.business import gmStaff 1850 return gmStaff.cStaff(aPK_obj = self._payload[self._idx['pk_primary_provider']])
1851 1852 primary_provider = property(_get_primary_provider, lambda x:x) 1853 1854 #---------------------------------------------------------------------- 1855 # convenience 1856 #----------------------------------------------------------------------
1857 - def get_subdir_name(self):
1858 """Format patient demographics into patient specific path name fragment.""" 1859 1860 return gmTools.fname_sanitize('%s-%s-%s' % ( 1861 self._payload[self._idx['lastnames']], 1862 self._payload[self._idx['firstnames']], 1863 self.get_formatted_dob(format = '%Y-%m-%d') 1864 ))
1865 # return (u'%s-%s-%s' % ( 1866 # self._payload[self._idx['lastnames']].replace(u' ', u'_'), 1867 # self._payload[self._idx['firstnames']].replace(u' ', u'_'), 1868 # self.get_formatted_dob(format = '%Y-%m-%d') 1869 # )).replace ( 1870 # u"'", u"" 1871 # ).replace ( 1872 # u'"', u'' 1873 # ).replace ( 1874 # u'/', u'_' 1875 # ).replace ( 1876 # u'\\', u'_' 1877 # ).replace ( 1878 # u'~', u'' 1879 # ).replace ( 1880 # u'|', u'_' 1881 # ).replace ( 1882 # u'*', u'' 1883 # ).replace ( 1884 # u'\u2248', u'' # "approximately", having been added by dob_is_estimated 1885 # ) 1886 1887 1888 subdir_name = property(get_subdir_name, lambda x:x)
1889 1890 #============================================================
1891 -def identity_is_patient(pk_identity):
1892 cmd = 'SELECT 1 FROM clin.patient WHERE fk_identity = %(pk_pat)s' 1893 args = {'pk_pat': pk_identity} 1894 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1895 if len(rows) == 0: 1896 return False 1897 return True
1898 1899 #------------------------------------------------------------
1900 -def turn_identity_into_patient(pk_identity):
1901 cmd = """ 1902 INSERT INTO clin.patient (fk_identity) 1903 SELECT %(pk_ident)s WHERE NOT EXISTS ( 1904 SELECT 1 FROM clin.patient c_p WHERE fk_identity = %(pk_ident)s 1905 )""" 1906 args = {'pk_ident': pk_identity} 1907 queries = [{'cmd': cmd, 'args': args}] 1908 gmPG2.run_rw_queries(queries = queries) 1909 return True
1910 1911 #============================================================ 1912 # helper functions 1913 #------------------------------------------------------------ 1914 _yield = lambda x:x 1915
1916 -def set_yielder(yielder):
1917 if not callable(yielder): 1918 raise TypeError('yielder <%s> is not callable' % yielder) 1919 global _yield 1920 _yield = yielder 1921 _log.debug('setting yielder to <%s>', yielder)
1922 1923 #============================================================
1924 -class cPatient(cPerson):
1925 """Represents a person which is a patient. 1926 1927 - a specializing subclass of cPerson turning it into a patient 1928 - its use is to cache subobjects like EMR and document folder 1929 """
1930 - def __init__(self, aPK_obj=None, row=None):
1931 cPerson.__init__(self, aPK_obj = aPK_obj, row = row) 1932 self.__emr_access_lock = threading.Lock() 1933 self.__emr = None 1934 self.__doc_folder = None
1935 1936 #--------------------------------------------------------
1937 - def cleanup(self):
1938 """Do cleanups before dying. 1939 1940 - note that this may be called in a thread 1941 """ 1942 if self.__emr is not None: 1943 self.__emr.cleanup() 1944 if self.__doc_folder is not None: 1945 self.__doc_folder.cleanup() 1946 cPerson.cleanup(self)
1947 1948 #----------------------------------------------------------------
1949 - def ensure_has_allergy_state(self, pk_encounter=None):
1950 from Gnumed.business.gmAllergy import ensure_has_allergy_state 1951 ensure_has_allergy_state(encounter = pk_encounter) 1952 return True
1953 1954 #----------------------------------------------------------
1955 - def get_emr(self):
1956 _log.debug('accessing EMR for identity [%s], thread [%s]', self._payload[self._idx['pk_identity']], threading.get_ident()) 1957 1958 # fast path: already set, just return it 1959 if self.__emr is not None: 1960 return self.__emr 1961 1962 stack_logged = False 1963 got_lock = self.__emr_access_lock.acquire(False) 1964 if not got_lock: 1965 # do some logging as we failed to get the lock 1966 call_stack = inspect.stack() 1967 call_stack.reverse() 1968 for idx in range(1, len(call_stack)): 1969 caller = call_stack[idx] 1970 _log.debug('%s[%s] @ [%s] in [%s]', ' '* idx, caller[3], caller[2], caller[1]) 1971 del call_stack 1972 stack_logged = True 1973 # now loop a bit 1974 for idx in range(500): 1975 _yield() 1976 time.sleep(0.1) 1977 _yield() 1978 got_lock = self.__emr_access_lock.acquire(False) 1979 if got_lock: 1980 break 1981 if not got_lock: 1982 _log.error('still failed to acquire EMR access lock, aborting (thread [%s])', threading.get_ident()) 1983 self.__emr_access_lock.release() 1984 raise AttributeError('cannot lock access to EMR for identity [%s]' % self._payload[self._idx['pk_identity']]) 1985 1986 _log.debug('pulling chart for identity [%s], thread [%s]', self._payload[self._idx['pk_identity']], threading.get_ident()) 1987 if not stack_logged: 1988 # do some logging as we are pulling the chart for the first time 1989 call_stack = inspect.stack() 1990 call_stack.reverse() 1991 for idx in range(1, len(call_stack)): 1992 caller = call_stack[idx] 1993 _log.debug('%s[%s] @ [%s] in [%s]', ' '* idx, caller[3], caller[2], caller[1]) 1994 del call_stack 1995 stack_logged = True 1996 1997 self.is_patient = True 1998 from Gnumed.business import gmClinicalRecord 1999 emr = gmClinicalRecord.cClinicalRecord(aPKey = self._payload[self._idx['pk_identity']]) 2000 2001 _log.debug('returning EMR for identity [%s], thread [%s]', self._payload[self._idx['pk_identity']], threading.get_ident()) 2002 self.__emr = emr 2003 self.__emr_access_lock.release() 2004 return self.__emr
2005 2006 emr = property(get_emr, lambda x:x) 2007 2008 #----------------------------------------------------------
2009 - def get_document_folder(self):
2010 if self.__doc_folder is None: 2011 self.__doc_folder = cDocumentFolder(aPKey = self._payload[self._idx['pk_identity']]) 2012 return self.__doc_folder
2013 2014 document_folder = property(get_document_folder, lambda x:x)
2015 2016 #============================================================
2017 -class gmCurrentPatient(gmBorg.cBorg):
2018 """Patient Borg to hold the currently active patient. 2019 2020 There may be many instances of this but they all share state. 2021 2022 The underlying dem.identity row must have .deleted set to FALSE. 2023 2024 The sequence of events when changing the active patient: 2025 2026 1) Registered callbacks are run. 2027 Those are run synchronously. If a callback 2028 returns False or throws an exception the 2029 patient switch is aborted. Callback code 2030 can rely on the patient still being active 2031 and to not go away until it returns. It 2032 is not passed any arguments and must return 2033 False or True. 2034 2035 2) Signal "pre_patient_unselection" is sent. 2036 This does not wait for nor check results. 2037 The keyword pk_identity contains the 2038 PK of the person being switched away 2039 from. 2040 2041 3) the current patient is unset (gmNull.cNull) 2042 2043 4) Signal "current_patient_unset" is sent 2044 At this point resetting GUI fields to 2045 empty should be done. The active patient 2046 is not there anymore. 2047 2048 This does not wait for nor check results. 2049 2050 5) The current patient is set to the new value. 2051 The new patient can also remain gmNull.cNull 2052 in case the calling code explicitely unset 2053 the current patient. 2054 2055 6) Signal "post_patient_selection" is sent. 2056 Code listening to this signal can 2057 assume that the new patient is 2058 already active. 2059 """
2060 - def __init__(self, patient=None, forced_reload=False):
2061 """Change or get currently active patient. 2062 2063 patient: 2064 * None: get currently active patient 2065 * -1: unset currently active patient 2066 * cPatient instance: set active patient if possible 2067 """ 2068 # make sure we do have a patient pointer 2069 try: 2070 self.patient 2071 except AttributeError: 2072 self.patient = gmNull.cNull() 2073 self.__register_interests() 2074 # set initial lock state, 2075 # this lock protects against activating another patient 2076 # when we are controlled from a remote application 2077 self.__lock_depth = 0 2078 # initialize callback state 2079 self.__callbacks_before_switching_away_from_patient = [] 2080 2081 # user wants copy of current patient 2082 if patient is None: 2083 return None 2084 2085 # do nothing if patient is locked 2086 if self.locked: 2087 _log.error('patient [%s] is locked, cannot change to [%s]' % (self.patient['pk_identity'], patient)) 2088 return None 2089 2090 # user wants to explicitly unset current patient 2091 if patient == -1: 2092 _log.debug('explicitly unsetting current patient') 2093 if not self.__run_callbacks_before_switching_away_from_patient(): 2094 _log.error('not unsetting current patient, at least one pre-change callback failed') 2095 return None 2096 self.__send_pre_unselection_notification() 2097 self.patient.cleanup() 2098 self.patient = gmNull.cNull() 2099 self.__send_unselection_notification() 2100 # give it some time 2101 time.sleep(0.5) 2102 self.__send_selection_notification() 2103 return None 2104 2105 # must be cPatient instance, then 2106 if not isinstance(patient, cPatient): 2107 _log.error('cannot set active patient to [%s], must be either None, -1 or cPatient instance' % str(patient)) 2108 raise TypeError('gmPerson.gmCurrentPatient.__init__(): <patient> must be None, -1 or cPatient instance but is: %s' % str(patient)) 2109 2110 # same ID, no change needed 2111 if (self.patient['pk_identity'] == patient['pk_identity']) and not forced_reload: 2112 return None 2113 2114 if patient['is_deleted']: 2115 _log.error('cannot set active patient to disabled dem.identity row: %s', patient) 2116 raise ValueError('gmPerson.gmCurrentPatient.__init__(): <patient> is disabled: %s' % patient) 2117 2118 # user wants different patient 2119 _log.info('patient change [%s] -> [%s] requested', self.patient['pk_identity'], patient['pk_identity']) 2120 2121 if not self.__run_callbacks_before_switching_away_from_patient(): 2122 _log.error('not changing current patient, at least one pre-change callback failed') 2123 return None 2124 2125 # everything seems swell 2126 self.__send_pre_unselection_notification() 2127 self.patient.cleanup() 2128 self.patient = gmNull.cNull() 2129 self.__send_unselection_notification() 2130 # give it some time 2131 time.sleep(0.5) 2132 self.patient = patient 2133 # for good measure ... 2134 # however, actually we want to get rid of that 2135 self.patient.emr 2136 self.__send_selection_notification() 2137 2138 return None
2139 2140 #--------------------------------------------------------
2141 - def __register_interests(self):
2142 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
2143 2144 #--------------------------------------------------------
2145 - def _on_database_signal(self, **kwds):
2146 # we don't have a patient: don't process signals 2147 if isinstance(self.patient, gmNull.cNull): 2148 return True 2149 2150 # we only care about identity and name changes 2151 if kwds['table'] not in ['dem.identity', 'dem.names']: 2152 return True 2153 2154 # signal is not about our patient: ignore signal 2155 if int(kwds['pk_identity']) != self.patient.ID: 2156 return True 2157 2158 if kwds['table'] == 'dem.identity': 2159 # we don't care about newly INSERTed or DELETEd patients 2160 if kwds['operation'] != 'UPDATE': 2161 return True 2162 2163 self.patient.refetch_payload() 2164 return True
2165 2166 #-------------------------------------------------------- 2167 # external API 2168 #--------------------------------------------------------
2169 - def register_before_switching_from_patient_callback(self, callback=None):
2170 # callbacks are run synchronously before 2171 # switching *away* from the current patient, 2172 # if a callback returns false the current 2173 # patient will not be switched away from, 2174 # callbacks will not be passed any arguments 2175 if not callable(callback): 2176 raise TypeError('callback [%s] not callable' % callback) 2177 2178 self.__callbacks_before_switching_away_from_patient.append(callback)
2179 2180 #--------------------------------------------------------
2181 - def _get_connected(self):
2182 return (not isinstance(self.patient, gmNull.cNull))
2183 2184 connected = property(_get_connected, lambda x:x) 2185 2186 #--------------------------------------------------------
2187 - def _get_locked(self):
2188 return (self.__lock_depth > 0)
2189
2190 - def _set_locked(self, locked):
2191 if locked: 2192 self.__lock_depth = self.__lock_depth + 1 2193 gmDispatcher.send(signal = 'patient_locked', sender = self.__class__.__name__) 2194 else: 2195 if self.__lock_depth == 0: 2196 _log.error('lock/unlock imbalance, tried to refcount lock depth below 0') 2197 return 2198 else: 2199 self.__lock_depth = self.__lock_depth - 1 2200 gmDispatcher.send(signal = 'patient_unlocked', sender = self.__class__.__name__)
2201 2202 locked = property(_get_locked, _set_locked) 2203 2204 #--------------------------------------------------------
2205 - def force_unlock(self):
2206 _log.info('forced patient unlock at lock depth [%s]' % self.__lock_depth) 2207 self.__lock_depth = 0 2208 gmDispatcher.send(signal = 'patient_unlocked', sender = self.__class__.__name__)
2209 2210 #-------------------------------------------------------- 2211 # patient change handling 2212 #--------------------------------------------------------
2214 if isinstance(self.patient, gmNull.cNull): 2215 return True 2216 2217 for call_back in self.__callbacks_before_switching_away_from_patient: 2218 try: 2219 successful = call_back() 2220 except: 2221 _log.exception('callback [%s] failed', call_back) 2222 print("*** pre-change callback failed ***") 2223 print(type(call_back)) 2224 print(call_back) 2225 return False 2226 2227 if not successful: 2228 _log.error('callback [%s] returned False', call_back) 2229 return False 2230 2231 return True
2232 2233 #--------------------------------------------------------
2235 """Sends signal when current patient is about to be unset. 2236 2237 This does NOT wait for signal handlers to complete. 2238 """ 2239 kwargs = { 2240 'signal': 'pre_patient_unselection', 2241 'sender': self.__class__.__name__, 2242 'pk_identity': self.patient['pk_identity'] 2243 } 2244 gmDispatcher.send(**kwargs)
2245 2246 #--------------------------------------------------------
2248 """Sends signal when the previously active patient has 2249 been unset during a change of active patient. 2250 2251 This is the time to initialize GUI fields to empty values. 2252 2253 This does NOT wait for signal handlers to complete. 2254 """ 2255 kwargs = { 2256 'signal': 'current_patient_unset', 2257 'sender': self.__class__.__name__ 2258 } 2259 gmDispatcher.send(**kwargs)
2260 2261 #--------------------------------------------------------
2263 """Sends signal when another patient has actually been made active.""" 2264 kwargs = { 2265 'signal': 'post_patient_selection', 2266 'sender': self.__class__.__name__, 2267 'pk_identity': self.patient['pk_identity'] 2268 } 2269 gmDispatcher.send(**kwargs)
2270 2271 #-------------------------------------------------------- 2272 # __getattr__ handling 2273 #--------------------------------------------------------
2274 - def __getattr__(self, attribute):
2275 # override __getattr__ here, not __getattribute__ because 2276 # the former is used _after_ ordinary attribute lookup 2277 # failed while the latter is applied _before_ ordinary 2278 # lookup (and is easy to drive into infinite recursion), 2279 # this is also why subsequent access to self.patient 2280 # simply returns the .patient member value :-) 2281 if attribute == 'patient': 2282 raise AttributeError 2283 if isinstance(self.patient, gmNull.cNull): 2284 _log.error("[%s]: cannot getattr(%s, '%s'), patient attribute not connected to a patient", self, self.patient, attribute) 2285 raise AttributeError("[%s]: cannot getattr(%s, '%s'), patient attribute not connected to a patient" % (self, self.patient, attribute)) 2286 return getattr(self.patient, attribute)
2287 2288 #-------------------------------------------------------- 2289 # __get/setitem__ handling 2290 #--------------------------------------------------------
2291 - def __getitem__(self, attribute = None):
2292 """Return any attribute if known how to retrieve it by proxy. 2293 """ 2294 return self.patient[attribute]
2295 2296 #--------------------------------------------------------
2297 - def __setitem__(self, attribute, value):
2298 self.patient[attribute] = value
2299 2300 #============================================================ 2301 # match providers 2302 #============================================================
2303 -class cMatchProvider_Provider(gmMatchProvider.cMatchProvider_SQL2):
2304 - def __init__(self):
2305 gmMatchProvider.cMatchProvider_SQL2.__init__( 2306 self, 2307 queries = [ 2308 """SELECT 2309 pk_staff AS data, 2310 short_alias || ' (' || coalesce(title, '') || ' ' || firstnames || ' ' || lastnames || ')' AS list_label, 2311 short_alias || ' (' || coalesce(title, '') || ' ' || firstnames || ' ' || lastnames || ')' AS field_label 2312 FROM dem.v_staff 2313 WHERE 2314 is_active AND ( 2315 short_alias %(fragment_condition)s OR 2316 firstnames %(fragment_condition)s OR 2317 lastnames %(fragment_condition)s OR 2318 db_user %(fragment_condition)s 2319 ) 2320 """ 2321 ] 2322 ) 2323 self.setThresholds(1, 2, 3)
2324 2325 #============================================================ 2326 # convenience functions 2327 #============================================================
2328 -def create_name(pk_person, firstnames, lastnames, active=False):
2329 queries = [{ 2330 'cmd': "select dem.add_name(%s, %s, %s, %s)", 2331 'args': [pk_person, firstnames, lastnames, active] 2332 }] 2333 rows, idx = gmPG2.run_rw_queries(queries=queries, return_data=True) 2334 name = cPersonName(aPK_obj = rows[0][0]) 2335 return name
2336 2337 #============================================================
2338 -def create_identity(gender=None, dob=None, lastnames=None, firstnames=None, comment=None):
2339 2340 cmd1 = "INSERT INTO dem.identity (gender, dob, comment) VALUES (%s, %s, %s)" 2341 cmd2 = """ 2342 INSERT INTO dem.names ( 2343 id_identity, lastnames, firstnames 2344 ) VALUES ( 2345 currval('dem.identity_pk_seq'), coalesce(%s, 'xxxDEFAULTxxx'), coalesce(%s, 'xxxDEFAULTxxx') 2346 ) RETURNING id_identity""" 2347 # cmd2 = u"select dem.add_name(currval('dem.identity_pk_seq')::integer, coalesce(%s, 'xxxDEFAULTxxx'), coalesce(%s, 'xxxDEFAULTxxx'), True)" 2348 try: 2349 rows, idx = gmPG2.run_rw_queries ( 2350 queries = [ 2351 {'cmd': cmd1, 'args': [gender, dob, comment]}, 2352 {'cmd': cmd2, 'args': [lastnames, firstnames]} 2353 #{'cmd': cmd2, 'args': [firstnames, lastnames]} 2354 ], 2355 return_data = True 2356 ) 2357 except Exception: 2358 _log.exception('cannot create identity') 2359 gmLog2.log_stack_trace() 2360 return None 2361 ident = cPerson(aPK_obj = rows[0][0]) 2362 gmHooks.run_hook_script(hook = 'post_person_creation') 2363 return ident
2364 2365 #============================================================
2366 -def disable_identity(pk_identity):
2367 _log.info('disabling identity [%s]', pk_identity) 2368 cmd = "UPDATE dem.identity SET deleted = true WHERE pk = %(pk)s" 2369 args = {'pk': pk_identity} 2370 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2371 return True
2372 2373 #============================================================
2374 -def create_dummy_identity():
2375 cmd = "INSERT INTO dem.identity(gender) VALUES (NULL::text) RETURNING pk" 2376 rows, idx = gmPG2.run_rw_queries ( 2377 queries = [{'cmd': cmd}], 2378 return_data = True 2379 ) 2380 return gmDemographicRecord.cPerson(aPK_obj = rows[0][0])
2381 2382 #============================================================
2383 -def identity_exists(pk_identity):
2384 cmd = 'SELECT EXISTS(SELECT 1 FROM dem.identity where pk = %(pk)s)' 2385 args = {'pk': pk_identity} 2386 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 2387 return rows[0][0]
2388 2389 #============================================================
2390 -def set_active_patient(patient=None, forced_reload=False):
2391 """Set active patient. 2392 2393 If patient is -1 the active patient will be UNset. 2394 """ 2395 if isinstance(patient, gmCurrentPatient): 2396 return True 2397 2398 if isinstance(patient, cPatient): 2399 pat = patient 2400 elif isinstance(patient, cPerson): 2401 pat = pat.as_patient 2402 elif patient == -1: 2403 pat = patient 2404 else: 2405 # maybe integer ? 2406 success, pk = gmTools.input2int(initial = patient, minval = 1) 2407 if not success: 2408 raise ValueError('<patient> must be either -1, >0, or a cPatient, cPerson or gmCurrentPatient instance, is: %s' % patient) 2409 # but also valid patient ID ? 2410 try: 2411 pat = cPatient(aPK_obj = pk) 2412 except: 2413 _log.exception('identity [%s] not found' % patient) 2414 return False 2415 2416 # attempt to switch 2417 try: 2418 gmCurrentPatient(patient = pat, forced_reload = forced_reload) 2419 except: 2420 _log.exception('error changing active patient to [%s]' % patient) 2421 return False 2422 2423 return True
2424 2425 #============================================================ 2426 # gender related 2427 #------------------------------------------------------------
2428 -def get_gender_list():
2429 """Retrieves the list of known genders from the database.""" 2430 global __gender_idx 2431 global __gender_list 2432 2433 if __gender_list is None: 2434 cmd = "SELECT tag, l10n_tag, label, l10n_label, sort_weight FROM dem.v_gender_labels ORDER BY sort_weight DESC" 2435 __gender_list, __gender_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 2436 _log.debug('genders in database: %s' % __gender_list) 2437 2438 return (__gender_list, __gender_idx)
2439 2440 #------------------------------------------------------------ 2441 map_gender2mf = { 2442 'm': 'm', 2443 'f': 'f', 2444 'tf': 'f', 2445 'tm': 'm', 2446 'h': 'mf' 2447 } 2448 2449 # https://tools.ietf.org/html/rfc6350#section-6.2.7 2450 # M F O N U 2451 map_gender2vcard = { 2452 'm': 'M', 2453 'f': 'F', 2454 'tf': 'F', 2455 'tm': 'M', 2456 'h': 'O', 2457 None: 'U' 2458 } 2459 2460 #------------------------------------------------------------ 2461 # maps GNUmed related i18n-aware gender specifiers to a unicode symbol 2462 map_gender2symbol = { 2463 'm': '\u2642', 2464 'f': '\u2640', 2465 'tf': '\u26A5\u2640', 2466 # 'tf': u'\u2642\u2640-\u2640', 2467 'tm': '\u26A5\u2642', 2468 # 'tm': u'\u2642\u2640-\u2642', 2469 'h': '\u26A5', 2470 # 'h': u'\u2642\u2640', 2471 None: '?\u26A5?' 2472 } 2473 #------------------------------------------------------------
2474 -def map_gender2string(gender=None):
2475 """Maps GNUmed related i18n-aware gender specifiers to a human-readable string.""" 2476 2477 global __gender2string_map 2478 2479 if __gender2string_map is None: 2480 genders, idx = get_gender_list() 2481 __gender2string_map = { 2482 'm': _('male'), 2483 'f': _('female'), 2484 'tf': '', 2485 'tm': '', 2486 'h': '', 2487 None: _('unknown gender') 2488 } 2489 for g in genders: 2490 __gender2string_map[g[idx['l10n_tag']]] = g[idx['l10n_label']] 2491 __gender2string_map[g[idx['tag']]] = g[idx['l10n_label']] 2492 _log.debug('gender -> string mapping: %s' % __gender2string_map) 2493 2494 return __gender2string_map[gender]
2495 #------------------------------------------------------------
2496 -def map_gender2salutation(gender=None):
2497 """Maps GNUmed related i18n-aware gender specifiers to a human-readable salutation.""" 2498 2499 global __gender2salutation_map 2500 2501 if __gender2salutation_map is None: 2502 genders, idx = get_gender_list() 2503 __gender2salutation_map = { 2504 'm': _('Mr'), 2505 'f': _('Mrs'), 2506 'tf': '', 2507 'tm': '', 2508 'h': '', 2509 None: '' 2510 } 2511 for g in genders: 2512 __gender2salutation_map[g[idx['l10n_tag']]] = __gender2salutation_map[g[idx['tag']]] 2513 __gender2salutation_map[g[idx['label']]] = __gender2salutation_map[g[idx['tag']]] 2514 __gender2salutation_map[g[idx['l10n_label']]] = __gender2salutation_map[g[idx['tag']]] 2515 _log.debug('gender -> salutation mapping: %s' % __gender2salutation_map) 2516 2517 return __gender2salutation_map[gender]
2518 #------------------------------------------------------------
2519 -def map_firstnames2gender(firstnames=None):
2520 """Try getting the gender for the given first name.""" 2521 2522 if firstnames is None: 2523 return None 2524 2525 rows, idx = gmPG2.run_ro_queries(queries = [{ 2526 'cmd': "SELECT gender FROM dem.name_gender_map WHERE name ILIKE %(fn)s LIMIT 1", 2527 'args': {'fn': firstnames} 2528 }]) 2529 2530 if len(rows) == 0: 2531 return None 2532 2533 return rows[0][0]
2534 #============================================================
2535 -def get_person_IDs():
2536 cmd = 'SELECT pk FROM dem.identity' 2537 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False) 2538 return [ r[0] for r in rows ]
2539 2540 #============================================================
2541 -def get_persons_from_pks(pks=None):
2542 return [ cPerson(aPK_obj = pk) for pk in pks ]
2543 #============================================================
2544 -def get_person_from_xdt(filename=None, encoding=None, dob_format=None):
2545 from Gnumed.business import gmXdtObjects 2546 return gmXdtObjects.read_person_from_xdt(filename=filename, encoding=encoding, dob_format=dob_format)
2547 #============================================================
2548 -def get_persons_from_pracsoft_file(filename=None, encoding='ascii'):
2549 from Gnumed.business import gmPracSoftAU 2550 return gmPracSoftAU.read_persons_from_pracsoft_file(filename=filename, encoding=encoding)
2551 2552 #============================================================ 2553 # main/testing 2554 #============================================================ 2555 if __name__ == '__main__': 2556 2557 if len(sys.argv) == 1: 2558 sys.exit() 2559 2560 if sys.argv[1] != 'test': 2561 sys.exit() 2562 2563 import datetime 2564 2565 gmI18N.activate_locale() 2566 gmI18N.install_domain() 2567 gmDateTime.init() 2568 2569 #--------------------------------------------------------
2570 - def test_set_active_pat():
2571 2572 ident = cPerson(1) 2573 print("setting active patient with", ident) 2574 set_active_patient(patient=ident) 2575 2576 patient = cPatient(12) 2577 print("setting active patient with", patient) 2578 set_active_patient(patient=patient) 2579 2580 pat = gmCurrentPatient() 2581 print(pat['dob']) 2582 #pat['dob'] = 'test' 2583 2584 # staff = cStaff() 2585 # print "setting active patient with", staff 2586 # set_active_patient(patient=staff) 2587 2588 print("setting active patient with -1") 2589 set_active_patient(patient=-1)
2590 #--------------------------------------------------------
2591 - def test_dto_person():
2592 dto = cDTO_person() 2593 dto.firstnames = 'Sepp' 2594 dto.lastnames = 'Herberger' 2595 dto.gender = 'male' 2596 dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone) 2597 print(dto) 2598 2599 print(dto['firstnames']) 2600 print(dto['lastnames']) 2601 print(dto['gender']) 2602 print(dto['dob']) 2603 2604 for key in dto.keys(): 2605 print(key)
2606 #--------------------------------------------------------
2607 - def test_identity():
2608 # create patient 2609 print('\n\nCreating identity...') 2610 new_identity = create_identity(gender='m', dob='2005-01-01', lastnames='test lastnames', firstnames='test firstnames') 2611 print('Identity created: %s' % new_identity) 2612 2613 print('\nSetting title and gender...') 2614 new_identity['title'] = 'test title'; 2615 new_identity['gender'] = 'f'; 2616 new_identity.save_payload() 2617 print('Refetching identity from db: %s' % cPerson(aPK_obj=new_identity['pk_identity'])) 2618 2619 print('\nGetting all names...') 2620 for a_name in new_identity.get_names(): 2621 print(a_name) 2622 print('Active name: %s' % (new_identity.get_active_name())) 2623 print('Setting nickname...') 2624 new_identity.set_nickname(nickname='test nickname') 2625 print('Refetching all names...') 2626 for a_name in new_identity.get_names(): 2627 print(a_name) 2628 print('Active name: %s' % (new_identity.get_active_name())) 2629 2630 print('\nIdentity occupations: %s' % new_identity['occupations']) 2631 print('Creating identity occupation...') 2632 new_identity.link_occupation('test occupation') 2633 print('Identity occupations: %s' % new_identity['occupations']) 2634 2635 print('\nIdentity addresses: %s' % new_identity.get_addresses()) 2636 print('Creating identity address...') 2637 # make sure the state exists in the backend 2638 new_identity.link_address ( 2639 number = 'test 1234', 2640 street = 'test street', 2641 postcode = 'test postcode', 2642 urb = 'test urb', 2643 region_code = 'SN', 2644 country_code = 'DE' 2645 ) 2646 print('Identity addresses: %s' % new_identity.get_addresses()) 2647 2648 print('\nIdentity communications: %s' % new_identity.get_comm_channels()) 2649 print('Creating identity communication...') 2650 new_identity.link_comm_channel('homephone', '1234566') 2651 print('Identity communications: %s' % new_identity.get_comm_channels())
2652 #--------------------------------------------------------
2653 - def test_name():
2654 for pk in range(1,16): 2655 name = cPersonName(aPK_obj=pk) 2656 print(name.description) 2657 print(' ', name)
2658 #--------------------------------------------------------
2659 - def test_gender_list():
2660 genders, idx = get_gender_list() 2661 print("\n\nRetrieving gender enum (tag, label, weight):") 2662 for gender in genders: 2663 print("%s, %s, %s" % (gender[idx['tag']], gender[idx['l10n_label']], gender[idx['sort_weight']]))
2664 #--------------------------------------------------------
2665 - def test_export_area():
2666 person = cPerson(aPK_obj = 12) 2667 print(person) 2668 print(person.export_area) 2669 print(person.export_area.items)
2670 #--------------------------------------------------------
2671 - def test_ext_id():
2672 person = cPerson(aPK_obj = 9) 2673 print(person.get_external_ids(id_type='Fachgebiet', issuer='Ärztekammer'))
2674 #print person.get_external_ids() 2675 #--------------------------------------------------------
2676 - def test_vcf():
2677 person = cPerson(aPK_obj = 12) 2678 print(person.export_as_vcard())
2679 2680 #--------------------------------------------------------
2681 - def test_current_patient():
2682 pat = gmCurrentPatient() 2683 print("pat.emr", pat.emr)
2684 2685 #--------------------------------------------------------
2686 - def test_ext_id():
2687 person = cPerson(aPK_obj = 12) 2688 print(person.suggest_external_id(target = 'Orthanc'))
2689 2690 #--------------------------------------------------------
2691 - def test_assimilate_identity():
2692 patient = cPatient(12) 2693 set_active_patient(patient = patient) 2694 curr_pat = gmCurrentPatient() 2695 other_pat = cIdentity(1111111) 2696 curr_pat.assimilate_identity(other_identity=None)
2697 2698 #-------------------------------------------------------- 2699 #test_dto_person() 2700 #test_identity() 2701 #test_set_active_pat() 2702 #test_search_by_dto() 2703 #test_name() 2704 #test_gender_list() 2705 2706 #map_gender2salutation('m') 2707 # module functions 2708 2709 #comms = get_comm_list() 2710 #print "\n\nRetrieving communication media enum (id, description): %s" % comms 2711 #test_export_area() 2712 #test_ext_id() 2713 #test_vcf() 2714 #test_ext_id() 2715 #test_current_patient() 2716 test_assimilate_identity() 2717 2718 #============================================================ 2719