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

Source Code for Module Gnumed.business.gmPerson

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