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

Source Code for Module Gnumed.business.gmPerson

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