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

Source Code for Module Gnumed.business.gmPerson

   1  # -*- coding: utf8 -*- 
   2  """GNUmed patient objects. 
   3   
   4  This is a patient object intended to let a useful client-side 
   5  API crystallize from actual use in true XP fashion. 
   6  """ 
   7  #============================================================ 
   8  __version__ = "$Revision: 1.198 $" 
   9  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
  10  __license__ = "GPL" 
  11   
  12  # std lib 
  13  import sys, os.path, time, re as regex, string, types, datetime as pyDT, codecs, threading, logging 
  14   
  15   
  16  # GNUmed 
  17  if __name__ == '__main__': 
  18          sys.path.insert(0, '../../') 
  19  from Gnumed.pycommon import gmExceptions, gmDispatcher, gmBorg, gmI18N, gmNull, gmBusinessDBObject, gmTools 
  20  from Gnumed.pycommon import gmPG2, gmMatchProvider, gmDateTime, gmLog2 
  21  from Gnumed.business import gmDocuments, gmDemographicRecord, gmProviderInbox, gmXdtMappings, gmClinicalRecord 
  22   
  23   
  24  _log = logging.getLogger('gm.person') 
  25  _log.info(__version__) 
  26   
  27  __gender_list = None 
  28  __gender_idx = None 
  29   
  30  __gender2salutation_map = None 
  31   
  32  #============================================================ 
33 -class cDTO_person(object):
34 35 # FIXME: make this work as a mapping type, too 36 37 #-------------------------------------------------------- 38 # external API 39 #--------------------------------------------------------
40 - def keys(self):
41 return 'firstnames lastnames dob gender'.split()
42 #--------------------------------------------------------
43 - def delete_from_source(self):
44 pass
45 #--------------------------------------------------------
46 - def get_candidate_identities(self, can_create=False):
47 """Generate generic queries. 48 49 - not locale dependant 50 - data -> firstnames, lastnames, dob, gender 51 52 shall we mogrify name parts ? probably not as external 53 sources should know what they do 54 55 finds by inactive name, too, but then shows 56 the corresponding active name ;-) 57 58 Returns list of matching identities (may be empty) 59 or None if it was told to create an identity but couldn't. 60 """ 61 where_snippets = [] 62 args = {} 63 64 where_snippets.append(u'firstnames = %(first)s') 65 args['first'] = self.firstnames 66 67 where_snippets.append(u'lastnames = %(last)s') 68 args['last'] = self.lastnames 69 70 if self.dob is not None: 71 where_snippets.append(u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %(dob)s)") 72 args['dob'] = self.dob 73 74 if self.gender is not None: 75 where_snippets.append('gender = %(sex)s') 76 args['sex'] = self.gender 77 78 cmd = u""" 79 select *, '%s' as match_type from dem.v_basic_person 80 where pk_identity in ( 81 select id_identity from dem.names where %s 82 ) order by lastnames, firstnames, dob""" % ( 83 _('external patient source (name, gender, date of birth)'), 84 ' and '.join(where_snippets) 85 ) 86 87 try: 88 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx=True) 89 except: 90 _log.error(u'cannot get candidate identities for dto "%s"' % self) 91 _log.exception('query %s' % cmd) 92 rows = [] 93 94 if len(rows) == 0: 95 if not can_create: 96 return [] 97 ident = self.import_into_database() 98 if ident is None: 99 return None 100 identities = [ident] 101 else: 102 identities = [ cIdentity(row = {'pk_field': 'pk_identity', 'data': row, 'idx': idx}) for row in rows ] 103 104 return identities
105 #--------------------------------------------------------
106 - def import_into_database(self):
107 """Imports self into the database. 108 109 Child classes can override this to provide more extensive import. 110 """ 111 ident = create_identity ( 112 firstnames = self.firstnames, 113 lastnames = self.lastnames, 114 gender = self.gender, 115 dob = self.dob 116 ) 117 return ident
118 #--------------------------------------------------------
119 - def import_extra_data(self, *args, **kwargs):
120 pass
121 #-------------------------------------------------------- 122 # customizing behaviour 123 #--------------------------------------------------------
124 - def __str__(self):
125 return u'<%s @ %s: %s %s (%s) %s>' % ( 126 self.__class__.__name__, 127 id(self), 128 self.firstnames, 129 self.lastnames, 130 self.gender, 131 self.dob 132 )
133 #--------------------------------------------------------
134 - def __setattr__(self, attr, val):
135 """Do some sanity checks on self.* access.""" 136 137 if attr == 'gender': 138 glist, idx = get_gender_list() 139 for gender in glist: 140 if str(val) in [gender[0], gender[1], gender[2], gender[3]]: 141 val = gender[idx['tag']] 142 object.__setattr__(self, attr, val) 143 return 144 raise ValueError('invalid gender: [%s]' % val) 145 146 if attr == 'dob': 147 if val is not None: 148 if not isinstance(val, pyDT.datetime): 149 raise TypeError('invalid type for DOB (must be datetime.datetime): %s [%s]' % (type(val), val)) 150 if val.tzinfo is None: 151 raise ValueError('datetime.datetime instance is lacking a time zone: [%s]' % val.isoformat()) 152 153 object.__setattr__(self, attr, val) 154 return
155 #--------------------------------------------------------
156 - def __getitem__(self, attr):
157 return getattr(self, attr)
158 #============================================================
159 -class cPersonName(gmBusinessDBObject.cBusinessDBObject):
160 _cmd_fetch_payload = u"select * from dem.v_person_names where pk_name = %s" 161 _cmds_store_payload = [ 162 u"""update dem.names set 163 active = False 164 where 165 %(active_name)s is True and -- act only when needed and only 166 id_identity = %(pk_identity)s and -- on names of this identity 167 active is True and -- which are active 168 id != %(pk_name)s -- but NOT *this* name 169 """, 170 u"""update dem.names set 171 active = %(active_name)s, 172 preferred = %(preferred)s, 173 comment = %(comment)s 174 where 175 id = %(pk_name)s and 176 id_identity = %(pk_identity)s and -- belt and suspenders 177 xmin = %(xmin_name)s""", 178 u"""select xmin as xmin_name from dem.names where id = %(pk_name)s""" 179 ] 180 _updatable_fields = ['active_name', 'preferred', 'comment'] 181 #--------------------------------------------------------
182 - def __setitem__(self, attribute, value):
183 if attribute == 'active_name': 184 # cannot *directly* deactivate a name, only indirectly 185 # by activating another one 186 # FIXME: should be done at DB level 187 if self._payload[self._idx['active_name']] is True: 188 return 189 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
190 #--------------------------------------------------------
191 - def _get_description(self):
192 return '%(last)s, %(title)s %(first)s%(nick)s' % { 193 'last': self._payload[self._idx['lastnames']], 194 'title': gmTools.coalesce ( 195 self._payload[self._idx['title']], 196 map_gender2salutation(self._payload[self._idx['gender']]) 197 ), 198 'first': self._payload[self._idx['firstnames']], 199 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u' "%s"', u'%s') 200 }
201 202 description = property(_get_description, lambda x:x)
203 #============================================================
204 -class cStaff(gmBusinessDBObject.cBusinessDBObject):
205 _cmd_fetch_payload = u"select * from dem.v_staff where pk_staff=%s" 206 _cmds_store_payload = [ 207 u"""update dem.staff set 208 fk_role = %(pk_role)s, 209 short_alias = %(short_alias)s, 210 comment = gm.nullify_empty_string(%(comment)s), 211 is_active = %(is_active)s, 212 db_user = %(db_user)s 213 where 214 pk=%(pk_staff)s and 215 xmin = %(xmin_staff)s""", 216 u"""select xmin_staff from dem.v_staff where pk_identity=%(pk_identity)s""" 217 ] 218 _updatable_fields = ['pk_role', 'short_alias', 'comment', 'is_active', 'db_user'] 219 #--------------------------------------------------------
220 - def __init__(self, aPK_obj=None, row=None):
221 # by default get staff corresponding to CURRENT_USER 222 if (aPK_obj is None) and (row is None): 223 cmd = u"select * from dem.v_staff where db_user = CURRENT_USER" 224 try: 225 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True) 226 except: 227 _log.exception('cannot instantiate staff instance') 228 gmLog2.log_stack_trace() 229 raise ValueError('cannot instantiate staff instance for database account CURRENT_USER') 230 if len(rows) == 0: 231 raise ValueError('no staff record for database account CURRENT_USER') 232 row = { 233 'pk_field': 'pk_staff', 234 'idx': idx, 235 'data': rows[0] 236 } 237 gmBusinessDBObject.cBusinessDBObject.__init__(self, row = row) 238 else: 239 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj = aPK_obj, row = row) 240 241 # are we SELF ? 242 self.__is_current_user = (gmPG2.get_current_user() == self._payload[self._idx['db_user']]) 243 244 self.__inbox = None
245 #--------------------------------------------------------
246 - def __setitem__(self, attribute, value):
247 if attribute == 'db_user': 248 if self.__is_current_user: 249 _log.debug('will not modify database account association of CURRENT_USER staff member') 250 return 251 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
252 #--------------------------------------------------------
253 - def _get_db_lang(self):
254 rows, idx = gmPG2.run_ro_queries ( 255 queries = [{ 256 'cmd': u'select i18n.get_curr_lang(%(usr)s)', 257 'args': {'usr': self._payload[self._idx['db_user']]} 258 }] 259 ) 260 return rows[0][0]
261
262 - def _set_db_lang(self, language):
263 if not gmPG2.set_user_language(language = language): 264 raise ValueError ( 265 u'Cannot set database language to [%s] for user [%s].' % (language, self._payload[self._idx['db_user']]) 266 ) 267 return
268 269 database_language = property(_get_db_lang, _set_db_lang) 270 #--------------------------------------------------------
271 - def _get_inbox(self):
272 if self.__inbox is None: 273 self.__inbox = gmProviderInbox.cProviderInbox(provider_id = self._payload[self._idx['pk_staff']]) 274 return self.__inbox
275
276 - def _set_inbox(self, inbox):
277 return
278 279 inbox = property(_get_inbox, _set_inbox)
280 #============================================================
281 -def set_current_provider_to_logged_on_user():
282 gmCurrentProvider(provider = cStaff())
283 #============================================================
284 -class gmCurrentProvider(gmBorg.cBorg):
285 """Staff member Borg to hold currently logged on provider. 286 287 There may be many instances of this but they all share state. 288 """
289 - def __init__(self, provider=None):
290 """Change or get currently logged on provider. 291 292 provider: 293 * None: get copy of current instance 294 * cStaff instance: change logged on provider (role) 295 """ 296 # make sure we do have a provider pointer 297 try: 298 self.provider 299 except AttributeError: 300 self.provider = gmNull.cNull() 301 302 # user wants copy of currently logged on provider 303 if provider is None: 304 return None 305 306 # must be cStaff instance, then 307 if not isinstance(provider, cStaff): 308 raise ValueError, 'cannot set logged on provider to [%s], must be either None or cStaff instance' % str(provider) 309 310 # same ID, no change needed 311 if self.provider['pk_staff'] == provider['pk_staff']: 312 return None 313 314 # first invocation 315 if isinstance(self.provider, gmNull.cNull): 316 self.provider = provider 317 return None 318 319 # user wants different provider 320 raise ValueError, 'provider change [%s] -> [%s] not yet supported' % (self.provider['pk_staff'], provider['pk_staff'])
321 322 #--------------------------------------------------------
323 - def get_staff(self):
324 return self.provider
325 #-------------------------------------------------------- 326 # __getitem__ handling 327 #--------------------------------------------------------
328 - def __getitem__(self, aVar):
329 """Return any attribute if known how to retrieve it by proxy. 330 """ 331 return self.provider[aVar]
332 #-------------------------------------------------------- 333 # __s/getattr__ handling 334 #--------------------------------------------------------
335 - def __getattr__(self, attribute):
336 if attribute == 'provider': # so we can __init__ ourselves 337 raise AttributeError 338 if not isinstance(self.provider, gmNull.cNull): 339 return getattr(self.provider, attribute)
340 # raise AttributeError 341 #============================================================
342 -class cIdentity(gmBusinessDBObject.cBusinessDBObject):
343 _cmd_fetch_payload = u"select * from dem.v_basic_person where pk_identity = %s" 344 _cmds_store_payload = [ 345 u"""update dem.identity set 346 gender = %(gender)s, 347 dob = %(dob)s, 348 tob = %(tob)s, 349 cob = gm.nullify_empty_string(%(cob)s), 350 title = gm.nullify_empty_string(%(title)s), 351 fk_marital_status = %(pk_marital_status)s, 352 karyotype = gm.nullify_empty_string(%(karyotype)s), 353 pupic = gm.nullify_empty_string(%(pupic)s), 354 deceased = %(deceased)s, 355 emergency_contact = gm.nullify_empty_string(%(emergency_contact)s), 356 fk_emergency_contact = %(pk_emergency_contact)s, 357 comment = gm.nullify_empty_string(%(comment)s) 358 where 359 pk = %(pk_identity)s and 360 xmin = %(xmin_identity)s""", 361 u"""select xmin_identity from dem.v_basic_person where pk_identity = %(pk_identity)s""" 362 ] 363 _updatable_fields = [ 364 "title", 365 "dob", 366 "tob", 367 "cob", 368 "gender", 369 "pk_marital_status", 370 "karyotype", 371 "pupic", 372 'deceased', 373 'emergency_contact', 374 'pk_emergency_contact', 375 'comment' 376 ] 377 #--------------------------------------------------------
378 - def _get_ID(self):
379 return self._payload[self._idx['pk_identity']]
380 - def _set_ID(self, value):
381 raise AttributeError('setting ID of identity is not allowed')
382 ID = property(_get_ID, _set_ID) 383 #--------------------------------------------------------
384 - def __setitem__(self, attribute, value):
385 386 if attribute == 'dob': 387 if value is not None: 388 389 if isinstance(value, pyDT.datetime): 390 if value.tzinfo is None: 391 raise ValueError('datetime.datetime instance is lacking a time zone: [%s]' % dt.isoformat()) 392 else: 393 raise TypeError, '[%s]: type [%s] (%s) invalid for attribute [dob], must be datetime.datetime or None' % (self.__class__.__name__, type(value), value) 394 395 # compare DOB at seconds level 396 if self._payload[self._idx['dob']] is not None: 397 old_dob = self._payload[self._idx['dob']].strftime('%Y %m %d %H %M %S') 398 new_dob = value.strftime('%Y %m %d %H %M %S') 399 if new_dob == old_dob: 400 return 401 402 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
403 #--------------------------------------------------------
404 - def cleanup(self):
405 pass
406 #--------------------------------------------------------
407 - def _get_is_patient(self):
408 cmd = u""" 409 select exists ( 410 select 1 411 from clin.v_emr_journal 412 where 413 pk_patient = %(pat)s 414 and 415 soap_cat is not null 416 )""" 417 args = {'pat': self._payload[self._idx['pk_identity']]} 418 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 419 return rows[0][0]
420
421 - def _set_is_patient(self, value):
422 raise AttributeError('setting is_patient status of identity is not allowed')
423 424 is_patient = property(_get_is_patient, _set_is_patient) 425 #-------------------------------------------------------- 426 # identity API 427 #--------------------------------------------------------
428 - def get_active_name(self):
429 for name in self.get_names(): 430 if name['active_name'] is True: 431 return name 432 433 _log.error('cannot retrieve active name for patient [%s]' % self._payload[self._idx['pk_identity']]) 434 return None
435 #--------------------------------------------------------
436 - def get_names(self):
437 cmd = u"select * from dem.v_person_names where pk_identity = %(pk_pat)s" 438 rows, idx = gmPG2.run_ro_queries ( 439 queries = [{ 440 'cmd': cmd, 441 'args': {'pk_pat': self._payload[self._idx['pk_identity']]} 442 }], 443 get_col_idx = True 444 ) 445 446 if len(rows) == 0: 447 # no names registered for patient 448 return [] 449 450 names = [ cPersonName(row = {'idx': idx, 'data': r, 'pk_field': 'pk_name'}) for r in rows ] 451 return names
452 #--------------------------------------------------------
453 - def get_formatted_dob(self, format='%x', encoding=None):
454 if self._payload[self._idx['dob']] is None: 455 return _('** DOB unknown **') 456 457 if encoding is None: 458 encoding = gmI18N.get_encoding() 459 460 return self._payload[self._idx['dob']].strftime(format).decode(encoding)
461 #--------------------------------------------------------
462 - def get_description_gender(self):
463 return '%(sex)s%(title)s %(last)s, %(first)s%(nick)s' % { 464 'last': self._payload[self._idx['lastnames']], 465 'first': self._payload[self._idx['firstnames']], 466 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u' (%s)', u'%s'), 467 'sex': map_gender2salutation(self._payload[self._idx['gender']]), 468 'title': gmTools.coalesce(self._payload[self._idx['title']], u'', u' %s', u'%s') 469 }
470 #--------------------------------------------------------
471 - def get_description(self):
472 return '%(last)s,%(title)s %(first)s%(nick)s' % { 473 'last': self._payload[self._idx['lastnames']], 474 'title': gmTools.coalesce(self._payload[self._idx['title']], u'', u' %s', u'%s'), 475 'first': self._payload[self._idx['firstnames']], 476 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u' (%s)', u'%s') 477 }
478 #--------------------------------------------------------
479 - def add_name(self, firstnames, lastnames, active=True):
480 """Add a name. 481 482 @param firstnames The first names. 483 @param lastnames The last names. 484 @param active When True, the new name will become the active one (hence setting other names to inactive) 485 @type active A types.BooleanType instance 486 """ 487 name = create_name(self.ID, firstnames, lastnames, active) 488 if active: 489 self.refetch_payload() 490 return name
491 #--------------------------------------------------------
492 - def delete_name(self, name=None):
493 cmd = u"delete from dem.names where id = %(name)s and id_identity = %(pat)s" 494 args = {'name': name['pk_name'], 'pat': self.ID} 495 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
496 # can't have been the active name as that would raise an 497 # exception (since no active name would be left) so no 498 # data refetch needed 499 #--------------------------------------------------------
500 - def set_nickname(self, nickname=None):
501 """ 502 Set the nickname. Setting the nickname only makes sense for the currently 503 active name. 504 @param nickname The preferred/nick/warrior name to set. 505 """ 506 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': u"select dem.set_nickname(%s, %s)", 'args': [self.ID, nickname]}]) 507 self.refetch_payload() 508 return True
509 #-------------------------------------------------------- 510 # external ID API 511 # 512 # since external IDs are not treated as first class 513 # citizens (classes in their own right, that is), we 514 # handle them *entirely* within cIdentity, also they 515 # only make sense with one single person (like names) 516 # and are not reused (like addresses), so they are 517 # truly added/deleted, not just linked/unlinked 518 #--------------------------------------------------------
519 - def add_external_id(self, type_name=None, value=None, issuer=None, comment=None, context=u'p', pk_type=None):
520 """Adds an external ID to the patient. 521 522 creates ID type if necessary 523 context hardcoded to 'p' for now 524 """ 525 526 # check for existing ID 527 if pk_type is not None: 528 cmd = u""" 529 select * from dem.v_external_ids4identity where 530 pk_identity = %(pat)s and 531 pk_type = %(pk_type)s and 532 value = %(val)s""" 533 else: 534 # by type/value/issuer 535 if issuer is None: 536 cmd = u""" 537 select * from dem.v_external_ids4identity where 538 pk_identity = %(pat)s and 539 name = %(name)s and 540 value = %(val)s""" 541 else: 542 cmd = u""" 543 select * from dem.v_external_ids4identity where 544 pk_identity = %(pat)s and 545 name = %(name)s and 546 value = %(val)s and 547 issuer = %(issuer)s""" 548 args = { 549 'pat': self.ID, 550 'name': type_name, 551 'val': value, 552 'issuer': issuer, 553 'pk_type': pk_type 554 } 555 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 556 557 # create new ID if not found 558 if len(rows) == 0: 559 560 args = { 561 'pat': self.ID, 562 'val': value, 563 'type_name': type_name, 564 'pk_type': pk_type, 565 'issuer': issuer, 566 'ctxt': context, 567 'comment': comment 568 } 569 570 if pk_type is None: 571 cmd = u"""insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values ( 572 %(val)s, 573 (select dem.add_external_id_type(%(type_name)s, %(issuer)s, %(ctxt)s)), 574 %(comment)s, 575 %(pat)s 576 )""" 577 else: 578 cmd = u"""insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values ( 579 %(val)s, 580 %(pk_type)s, 581 %(comment)s, 582 %(pat)s 583 )""" 584 585 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 586 587 # or update comment of existing ID 588 else: 589 row = rows[0] 590 if comment is not None: 591 # comment not already there ? 592 if gmTools.coalesce(row['comment'], '').find(comment.strip()) == -1: 593 comment = '%s%s' % (gmTools.coalesce(row['comment'], '', '%s // '), comment.strip) 594 cmd = u"update dem.lnk_identity2ext_id set comment = %(comment)s where id=%(pk)s" 595 args = {'comment': comment, 'pk': row['pk_id']} 596 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
597 #--------------------------------------------------------
598 - def update_external_id(self, pk_id=None, type=None, value=None, issuer=None, comment=None):
599 """Edits an existing external ID. 600 601 creates ID type if necessary 602 context hardcoded to 'p' for now 603 """ 604 cmd = u""" 605 update dem.lnk_identity2ext_id set 606 fk_origin = (select dem.add_external_id_type(%(type)s, %(issuer)s, %(ctxt)s)), 607 external_id = %(value)s, 608 comment = %(comment)s 609 where id = %(pk)s""" 610 args = {'pk': pk_id, 'ctxt': u'p', 'value': value, 'type': type, 'issuer': issuer, 'comment': comment} 611 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
612 #--------------------------------------------------------
613 - def get_external_ids(self, id_type=None, issuer=None, context=None):
614 where_parts = ['pk_identity = %(pat)s'] 615 args = {'pat': self.ID} 616 617 if id_type is not None: 618 where_parts.append(u'name = %(name)s') 619 args['name'] = id_type.strip() 620 621 if issuer is not None: 622 where_parts.append(u'issuer = %(issuer)s') 623 args['issuer'] = issuer.strip() 624 625 if context is not None: 626 where_parts.append(u'context = %(ctxt)s') 627 args['ctxt'] = context.strip() 628 629 cmd = u"select * from dem.v_external_ids4identity where %s" % ' and '.join(where_parts) 630 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 631 632 return rows
633 #--------------------------------------------------------
634 - def delete_external_id(self, pk_ext_id=None):
635 cmd = u""" 636 delete from dem.lnk_identity2ext_id 637 where id_identity = %(pat)s and id = %(pk)s""" 638 args = {'pat': self.ID, 'pk': pk_ext_id} 639 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
640 #--------------------------------------------------------
641 - def assimilate_identity(self, other_identity=None, link_obj=None):
642 """Merge another identity into this one. 643 644 Keep this one. Delete other one.""" 645 646 if other_identity.ID == self.ID: 647 return True, None 648 649 curr_pat = gmCurrentPatient() 650 if curr_pat.connected: 651 if other_identity.ID == curr_pat.ID: 652 return False, _('Cannot merge active patient into another patient.') 653 654 queries = [] 655 args = {'old_pat': other_identity.ID, 'new_pat': self.ID} 656 657 # delete old allergy state 658 queries.append ({ 659 '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)', 660 'args': args 661 }) 662 # FIXME: adjust allergy_state in kept patient 663 664 # deactivate all names of old patient 665 queries.append ({ 666 'cmd': u'update dem.names set active = False where id_identity = %(old_pat)s', 667 'args': args 668 }) 669 670 # find FKs pointing to identity 671 FKs = gmPG2.get_foreign_keys2column ( 672 schema = u'dem', 673 table = u'identity', 674 column = u'pk' 675 ) 676 677 # generate UPDATEs 678 cmd_template = u'update %s set %s = %%(new_pat)s where %s = %%(old_pat)s' 679 for FK in FKs: 680 queries.append ({ 681 'cmd': cmd_template % (FK['referencing_table'], FK['referencing_column'], FK['referencing_column']), 682 'args': args 683 }) 684 685 # remove old identity entry 686 queries.append ({ 687 'cmd': u'delete from dem.identity where pk = %(old_pat)s', 688 'args': args 689 }) 690 691 _log.warning('identity [%s] is about to assimilate identity [%s]', self.ID, other_identity.ID) 692 693 gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, end_tx = True) 694 695 self.add_external_id ( 696 type_name = u'merged GNUmed identity primary key', 697 value = u'GNUmed::pk::%s' % other_identity.ID, 698 issuer = u'GNUmed' 699 ) 700 701 return True, None
702 #-------------------------------------------------------- 703 #--------------------------------------------------------
704 - def put_on_waiting_list(self, urgency=0, comment=None, zone=None):
705 cmd = u""" 706 insert into clin.waiting_list (fk_patient, urgency, comment, area, list_position) 707 values ( 708 %(pat)s, 709 %(urg)s, 710 %(cmt)s, 711 %(area)s, 712 (select coalesce((max(list_position) + 1), 1) from clin.waiting_list) 713 )""" 714 args = {'pat': self.ID, 'urg': urgency, 'cmt': comment, 'area': zone} 715 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], verbose=True)
716 #--------------------------------------------------------
717 - def export_as_gdt(self, filename=None, encoding='iso-8859-15', external_id_type=None):
718 719 template = u'%s%s%s\r\n' 720 721 file = codecs.open ( 722 filename = filename, 723 mode = 'wb', 724 encoding = encoding, 725 errors = 'strict' 726 ) 727 728 file.write(template % (u'013', u'8000', u'6301')) 729 file.write(template % (u'013', u'9218', u'2.10')) 730 if external_id_type is None: 731 file.write(template % (u'%03d' % (9 + len(str(self.ID))), u'3000', self.ID)) 732 else: 733 ext_ids = self.get_external_ids(id_type = external_id_type) 734 if len(ext_ids) > 0: 735 file.write(template % (u'%03d' % (9 + len(ext_ids[0]['value'])), u'3000', ext_ids[0]['value'])) 736 file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['lastnames']])), u'3101', self._payload[self._idx['lastnames']])) 737 file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['firstnames']])), u'3102', self._payload[self._idx['firstnames']])) 738 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'))) 739 file.write(template % (u'010', u'3110', gmXdtMappings.map_gender_gm2xdt[self._payload[self._idx['gender']]])) 740 file.write(template % (u'025', u'6330', 'GNUmed::9206::encoding')) 741 file.write(template % (u'%03d' % (9 + len(encoding)), u'6331', encoding)) 742 if external_id_type is None: 743 file.write(template % (u'029', u'6332', u'GNUmed::3000::source')) 744 file.write(template % (u'017', u'6333', u'internal')) 745 else: 746 if len(ext_ids) > 0: 747 file.write(template % (u'029', u'6332', u'GNUmed::3000::source')) 748 file.write(template % (u'%03d' % (9 + len(external_id_type)), u'6333', external_id_type)) 749 750 file.close()
751 #-------------------------------------------------------- 752 # occupations API 753 #--------------------------------------------------------
754 - def get_occupations(self):
755 cmd = u"select * from dem.v_person_jobs where pk_identity=%s" 756 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}]) 757 return rows
758 #-------------------------------------------------------- 795 #-------------------------------------------------------- 803 #-------------------------------------------------------- 804 # comms API 805 #--------------------------------------------------------
806 - def get_comm_channels(self, comm_medium=None):
807 cmd = u"select * from dem.v_person_comms where pk_identity = %s" 808 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx = True) 809 810 filtered = rows 811 812 if comm_medium is not None: 813 filtered = [] 814 for row in rows: 815 if row['comm_type'] == comm_medium: 816 filtered.append(row) 817 818 return [ gmDemographicRecord.cCommChannel(row = { 819 'pk_field': 'pk_lnk_identity2comm', 820 'data': r, 821 'idx': idx 822 }) for r in filtered 823 ]
824 #-------------------------------------------------------- 842 #-------------------------------------------------------- 848 #-------------------------------------------------------- 849 # contacts API 850 #--------------------------------------------------------
851 - def get_addresses(self, address_type=None):
852 cmd = u"select * from dem.v_pat_addresses where pk_identity=%s" 853 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx=True) 854 addresses = [] 855 for r in rows: 856 addresses.append(gmDemographicRecord.cPatientAddress(row={'idx': idx, 'data': r, 'pk_field': 'pk_address'})) 857 858 filtered = addresses 859 860 if address_type is not None: 861 filtered = [] 862 for adr in addresses: 863 if adr['address_type'] == address_type: 864 filtered.append(adr) 865 866 return filtered
867 #-------------------------------------------------------- 915 #---------------------------------------------------------------------- 925 #---------------------------------------------------------------------- 926 # relatives API 927 #----------------------------------------------------------------------
928 - def get_relatives(self):
929 cmd = u""" 930 select 931 t.description, 932 vbp.pk_identity as id, 933 title, 934 firstnames, 935 lastnames, 936 dob, 937 cob, 938 gender, 939 karyotype, 940 pupic, 941 pk_marital_status, 942 marital_status,+ 943 xmin_identity, 944 preferred 945 from 946 dem.v_basic_person vbp, dem.relation_types t, dem.lnk_person2relative l 947 where 948 ( 949 l.id_identity = %(pk)s and 950 vbp.pk_identity = l.id_relative and 951 t.id = l.id_relation_type 952 ) or ( 953 l.id_relative = %(pk)s and 954 vbp.pk_identity = l.id_identity and 955 t.inverse = l.id_relation_type 956 )""" 957 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 958 if len(rows) == 0: 959 return [] 960 return [(row[0], cIdentity(row = {'data': row[1:], 'idx':idx, 'pk_field': 'pk'})) for row in rows]
961 #-------------------------------------------------------- 981 #----------------------------------------------------------------------
982 - def delete_relative(self, relation):
983 # unlink only, don't delete relative itself 984 self.set_relative(None, relation)
985 #---------------------------------------------------------------------- 986 # age/dob related 987 #----------------------------------------------------------------------
988 - def get_medical_age(self):
989 dob = self['dob'] 990 991 if dob is None: 992 return u'??' 993 994 if self['deceased'] is None: 995 # return gmDateTime.format_interval_medically ( 996 # pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone) - dob 997 # ) 998 return gmDateTime.format_apparent_age_medically ( 999 age = gmDateTime.calculate_apparent_age(start = dob) 1000 ) 1001 1002 return u'%s%s' % ( 1003 gmTools.u_latin_cross, 1004 # gmDateTime.format_interval_medically(self['deceased'] - dob) 1005 gmDateTime.format_apparent_age_medically ( 1006 age = gmDateTime.calculate_apparent_age ( 1007 start = dob, 1008 end = self['deceased'] 1009 ) 1010 ) 1011 )
1012 #----------------------------------------------------------------------
1013 - def dob_in_range(self, min_distance=u'1 week', max_distance=u'1 week'):
1014 cmd = u'select dem.dob_is_in_range(%(dob)s, %(min)s, %(max)s)' 1015 rows, idx = gmPG2.run_ro_queries ( 1016 queries = [{ 1017 'cmd': cmd, 1018 'args': {'dob': self['dob'], 'min': min_distance, 'max': max_distance} 1019 }] 1020 ) 1021 return rows[0][0]
1022 #---------------------------------------------------------------------- 1023 # practice related 1024 #----------------------------------------------------------------------
1025 - def get_last_encounter(self):
1026 cmd = u'select * from clin.v_most_recent_encounters where pk_patient=%s' 1027 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self._payload[self._idx['pk_identity']]]}]) 1028 if len(rows) > 0: 1029 return rows[0] 1030 else: 1031 return None
1032 #--------------------------------------------------------
1033 - def _get_messages(self):
1034 return gmProviderInbox.get_inbox_messages(pk_patient = self._payload[self._idx['pk_identity']])
1035
1036 - def _set_messages(self, messages):
1037 return
1038 1039 messages = property(_get_messages, _set_messages) 1040 #--------------------------------------------------------
1041 - def delete_message(self, pk=None):
1043 #---------------------------------------------------------------------- 1044 # convenience 1045 #----------------------------------------------------------------------
1046 - def get_dirname(self):
1047 """Format patient demographics into patient specific path name fragment.""" 1048 return '%s-%s%s-%s' % ( 1049 self._payload[self._idx['lastnames']].replace(u' ', u'_'), 1050 self._payload[self._idx['firstnames']].replace(u' ', u'_'), 1051 gmTools.coalesce(self._payload[self._idx['preferred']], u'', template_initial = u'-(%s)'), 1052 self.get_formatted_dob(format = '%Y-%m-%d', encoding = gmI18N.get_encoding()) 1053 )
1054 #============================================================
1055 -class cStaffMember(cIdentity):
1056 """Represents a staff member which is a person. 1057 1058 - a specializing subclass of cIdentity turning it into a staff member 1059 """
1060 - def __init__(self, identity = None):
1061 cIdentity.__init__(self, identity=identity) 1062 self.__db_cache = {}
1063 #--------------------------------------------------------
1064 - def get_inbox(self):
1065 return gmProviderInbox.cProviderInbox(provider_id = self.ID)
1066 #============================================================
1067 -class cPatient(cIdentity):
1068 """Represents a person which is a patient. 1069 1070 - a specializing subclass of cIdentity turning it into a patient 1071 - its use is to cache subobjects like EMR and document folder 1072 """
1073 - def __init__(self, aPK_obj=None, row=None):
1074 cIdentity.__init__(self, aPK_obj=aPK_obj, row=row) 1075 self.__db_cache = {} 1076 self.__emr_access_lock = threading.Lock()
1077 #--------------------------------------------------------
1078 - def cleanup(self):
1079 """Do cleanups before dying. 1080 1081 - note that this may be called in a thread 1082 """ 1083 if self.__db_cache.has_key('clinical record'): 1084 self.__db_cache['clinical record'].cleanup() 1085 if self.__db_cache.has_key('document folder'): 1086 self.__db_cache['document folder'].cleanup() 1087 cIdentity.cleanup(self)
1088 #----------------------------------------------------------
1089 - def get_emr(self):
1090 if not self.__emr_access_lock.acquire(False): 1091 raise AttributeError('cannot access EMR') 1092 try: 1093 emr = self.__db_cache['clinical record'] 1094 self.__emr_access_lock.release() 1095 return emr 1096 except KeyError: 1097 pass 1098 1099 self.__db_cache['clinical record'] = gmClinicalRecord.cClinicalRecord(aPKey = self._payload[self._idx['pk_identity']]) 1100 self.__emr_access_lock.release() 1101 return self.__db_cache['clinical record']
1102 #--------------------------------------------------------
1103 - def get_document_folder(self):
1104 try: 1105 return self.__db_cache['document folder'] 1106 except KeyError: 1107 pass 1108 1109 self.__db_cache['document folder'] = gmDocuments.cDocumentFolder(aPKey = self._payload[self._idx['pk_identity']]) 1110 return self.__db_cache['document folder']
1111 #============================================================
1112 -class gmCurrentPatient(gmBorg.cBorg):
1113 """Patient Borg to hold currently active patient. 1114 1115 There may be many instances of this but they all share state. 1116 """
1117 - def __init__(self, patient=None, forced_reload=False):
1118 """Change or get currently active patient. 1119 1120 patient: 1121 * None: get currently active patient 1122 * -1: unset currently active patient 1123 * cPatient instance: set active patient if possible 1124 """ 1125 # make sure we do have a patient pointer 1126 try: 1127 tmp = self.patient 1128 except AttributeError: 1129 self.patient = gmNull.cNull() 1130 self.__register_interests() 1131 # set initial lock state, 1132 # this lock protects against activating another patient 1133 # when we are controlled from a remote application 1134 self.__lock_depth = 0 1135 # initialize callback state 1136 self.__pre_selection_callbacks = [] 1137 1138 # user wants copy of current patient 1139 if patient is None: 1140 return None 1141 1142 # do nothing if patient is locked 1143 if self.locked: 1144 _log.error('patient [%s] is locked, cannot change to [%s]' % (self.patient['pk_identity'], patient)) 1145 return None 1146 1147 # user wants to explicitly unset current patient 1148 if patient == -1: 1149 _log.debug('explicitly unsetting current patient') 1150 if not self.__run_pre_selection_callbacks(): 1151 _log.debug('not unsetting current patient') 1152 return None 1153 self.__send_pre_selection_notification() 1154 self.patient.cleanup() 1155 self.patient = gmNull.cNull() 1156 self.__send_selection_notification() 1157 return None 1158 1159 # must be cPatient instance, then 1160 if not isinstance(patient, cPatient): 1161 _log.error('cannot set active patient to [%s], must be either None, -1 or cPatient instance' % str(patient)) 1162 raise TypeError, 'gmPerson.gmCurrentPatient.__init__(): <patient> must be None, -1 or cPatient instance but is: %s' % str(patient) 1163 1164 # same ID, no change needed 1165 if (self.patient['pk_identity'] == patient['pk_identity']) and not forced_reload: 1166 return None 1167 1168 # user wants different patient 1169 _log.debug('patient change [%s] -> [%s] requested', self.patient['pk_identity'], patient['pk_identity']) 1170 1171 # everything seems swell 1172 if not self.__run_pre_selection_callbacks(): 1173 _log.debug('not changing current patient') 1174 return None 1175 self.__send_pre_selection_notification() 1176 self.patient.cleanup() 1177 self.patient = patient 1178 self.patient.get_emr() 1179 self.__send_selection_notification() 1180 1181 return None
1182 #--------------------------------------------------------
1183 - def __register_interests(self):
1184 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_identity_change) 1185 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_identity_change)
1186 #--------------------------------------------------------
1187 - def _on_identity_change(self):
1188 """Listen for patient *data* change.""" 1189 self.patient.refetch_payload()
1190 #-------------------------------------------------------- 1191 # external API 1192 #--------------------------------------------------------
1193 - def register_pre_selection_callback(self, callback=None):
1194 if not callable(callback): 1195 raise TypeError(u'callback [%s] not callable' % callback) 1196 1197 self.__pre_selection_callbacks.append(callback)
1198 #--------------------------------------------------------
1199 - def _get_connected(self):
1200 return (not isinstance(self.patient, gmNull.cNull))
1201
1202 - def _set_connected(self):
1203 raise AttributeError(u'invalid to set <connected> state')
1204 1205 connected = property(_get_connected, _set_connected) 1206 #--------------------------------------------------------
1207 - def _get_locked(self):
1208 return (self.__lock_depth > 0)
1209
1210 - def _set_locked(self, locked):
1211 if locked: 1212 self.__lock_depth = self.__lock_depth + 1 1213 gmDispatcher.send(signal='patient_locked') 1214 else: 1215 if self.__lock_depth == 0: 1216 _log.error('lock/unlock imbalance, trying to refcount lock depth below 0') 1217 return 1218 else: 1219 self.__lock_depth = self.__lock_depth - 1 1220 gmDispatcher.send(signal='patient_unlocked')
1221 1222 locked = property(_get_locked, _set_locked) 1223 #--------------------------------------------------------
1224 - def force_unlock(self):
1225 _log.info('forced patient unlock at lock depth [%s]' % self.__lock_depth) 1226 self.__lock_depth = 0 1227 gmDispatcher.send(signal='patient_unlocked')
1228 #-------------------------------------------------------- 1229 # patient change handling 1230 #--------------------------------------------------------
1232 if isinstance(self.patient, gmNull.cNull): 1233 return True 1234 1235 for call_back in self.__pre_selection_callbacks: 1236 try: 1237 successful = call_back() 1238 except: 1239 _log.exception('callback [%s] failed', call_back) 1240 print "*** pre-selection callback failed ***" 1241 print type(call_back) 1242 print call_back 1243 return False 1244 1245 if not successful: 1246 _log.debug('callback [%s] returned False', call_back) 1247 return False 1248 1249 return True
1250 #--------------------------------------------------------
1252 """Sends signal when another patient is about to become active. 1253 1254 This does NOT wait for signal handlers to complete. 1255 """ 1256 kwargs = { 1257 'signal': u'pre_patient_selection', 1258 'sender': id(self.__class__), 1259 'pk_identity': self.patient['pk_identity'] 1260 } 1261 gmDispatcher.send(**kwargs)
1262 #--------------------------------------------------------
1264 """Sends signal when another patient has actually been made active.""" 1265 kwargs = { 1266 'signal': u'post_patient_selection', 1267 'sender': id(self.__class__), 1268 'pk_identity': self.patient['pk_identity'] 1269 } 1270 gmDispatcher.send(**kwargs)
1271 #-------------------------------------------------------- 1272 # __getattr__ handling 1273 #--------------------------------------------------------
1274 - def __getattr__(self, attribute):
1275 if attribute == 'patient': 1276 raise AttributeError 1277 if not isinstance(self.patient, gmNull.cNull): 1278 return getattr(self.patient, attribute)
1279 #-------------------------------------------------------- 1280 # __get/setitem__ handling 1281 #--------------------------------------------------------
1282 - def __getitem__(self, attribute = None):
1283 """Return any attribute if known how to retrieve it by proxy. 1284 """ 1285 return self.patient[attribute]
1286 #--------------------------------------------------------
1287 - def __setitem__(self, attribute, value):
1288 self.patient[attribute] = value
1289 #============================================================
1290 -class cPatientSearcher_SQL:
1291 """UI independant i18n aware patient searcher."""
1292 - def __init__(self):
1293 self._generate_queries = self._generate_queries_de 1294 # make a cursor 1295 self.conn = gmPG2.get_connection() 1296 self.curs = self.conn.cursor()
1297 #--------------------------------------------------------
1298 - def __del__(self):
1299 try: 1300 self.curs.close() 1301 except: pass 1302 try: 1303 self.conn.close() 1304 except: pass
1305 #-------------------------------------------------------- 1306 # public API 1307 #--------------------------------------------------------
1308 - def get_patients(self, search_term = None, a_locale = None, dto = None):
1309 identities = self.get_identities(search_term, a_locale, dto) 1310 if identities is None: 1311 return None 1312 return [cPatient(aPK_obj=ident['pk_identity']) for ident in identities]
1313 #--------------------------------------------------------
1314 - def get_identities(self, search_term = None, a_locale = None, dto = None):
1315 """Get patient identity objects for given parameters. 1316 1317 - either search term or search dict 1318 - dto contains structured data that doesn't need to be parsed (cDTO_person) 1319 - dto takes precedence over search_term 1320 """ 1321 parse_search_term = (dto is None) 1322 1323 if not parse_search_term: 1324 queries = self._generate_queries_from_dto(dto) 1325 if queries is None: 1326 parse_search_term = True 1327 if len(queries) == 0: 1328 parse_search_term = True 1329 1330 if parse_search_term: 1331 # temporary change of locale for selecting query generator 1332 if a_locale is not None: 1333 print "temporary change of locale on patient search not implemented" 1334 _log.warning("temporary change of locale on patient search not implemented") 1335 # generate queries 1336 if search_term is None: 1337 raise ValueError('need search term (dto AND search_term are None)') 1338 1339 queries = self._generate_queries(search_term) 1340 1341 # anything to do ? 1342 if len(queries) == 0: 1343 _log.error('query tree empty') 1344 _log.error('[%s] [%s] [%s]' % (search_term, a_locale, str(dto))) 1345 return None 1346 1347 # collect IDs here 1348 identities = [] 1349 # cycle through query list 1350 for query in queries: 1351 _log.debug("running %s" % query) 1352 try: 1353 rows, idx = gmPG2.run_ro_queries(queries = [query], get_col_idx=True) 1354 except: 1355 _log.exception('error running query') 1356 continue 1357 if len(rows) == 0: 1358 continue 1359 identities.extend ( 1360 [ cIdentity(row = {'pk_field': 'pk_identity', 'data': row, 'idx': idx}) for row in rows ] 1361 ) 1362 1363 pks = [] 1364 unique_identities = [] 1365 for identity in identities: 1366 if identity['pk_identity'] in pks: 1367 continue 1368 pks.append(identity['pk_identity']) 1369 unique_identities.append(identity) 1370 1371 return unique_identities
1372 #-------------------------------------------------------- 1373 # internal helpers 1374 #--------------------------------------------------------
1375 - def _normalize_soundalikes(self, aString = None, aggressive = False):
1376 """Transform some characters into a regex.""" 1377 if aString.strip() == u'': 1378 return aString 1379 1380 # umlauts 1381 normalized = aString.replace(u'Ä', u'(Ä|AE|Ae|A|E)') 1382 normalized = normalized.replace(u'Ö', u'(Ö|OE|Oe|O)') 1383 normalized = normalized.replace(u'Ü', u'(Ü|UE|Ue|U)') 1384 normalized = normalized.replace(u'ä', u'(ä|ae|e|a)') 1385 normalized = normalized.replace(u'ö', u'(ö|oe|o)') 1386 normalized = normalized.replace(u'ü', u'(ü|ue|u|y)') 1387 normalized = normalized.replace(u'ß', u'(ß|sz|ss|s)') 1388 1389 # common soundalikes 1390 # - René, Desiré, Inés ... 1391 normalized = normalized.replace(u'é', u'***DUMMY***') 1392 normalized = normalized.replace(u'è', u'***DUMMY***') 1393 normalized = normalized.replace(u'***DUMMY***', u'(é|e|è|ä|ae)') 1394 1395 # FIXME: missing i/a/o - but uncommon in German 1396 normalized = normalized.replace(u'v', u'***DUMMY***') 1397 normalized = normalized.replace(u'f', u'***DUMMY***') 1398 normalized = normalized.replace(u'ph', u'***DUMMY***') # now, this is *really* specific for German 1399 normalized = normalized.replace(u'***DUMMY***', u'(v|f|ph)') 1400 1401 # silent characters (Thomas vs Tomas) 1402 normalized = normalized.replace(u'Th',u'***DUMMY***') 1403 normalized = normalized.replace(u'T', u'***DUMMY***') 1404 normalized = normalized.replace(u'***DUMMY***', u'(Th|T)') 1405 normalized = normalized.replace(u'th', u'***DUMMY***') 1406 normalized = normalized.replace(u't', u'***DUMMY***') 1407 normalized = normalized.replace(u'***DUMMY***', u'(th|t)') 1408 1409 # apostrophes, hyphens et al 1410 normalized = normalized.replace(u'"', u'***DUMMY***') 1411 normalized = normalized.replace(u"'", u'***DUMMY***') 1412 normalized = normalized.replace(u'`', u'***DUMMY***') 1413 normalized = normalized.replace(u'***DUMMY***', u"""("|'|`|***DUMMY***|\s)*""") 1414 normalized = normalized.replace(u'-', u"""(-|\s)*""") 1415 normalized = normalized.replace(u'|***DUMMY***|', u'|-|') 1416 1417 if aggressive: 1418 pass 1419 # some more here 1420 1421 _log.debug('[%s] -> [%s]' % (aString, normalized)) 1422 1423 return normalized
1424 #-------------------------------------------------------- 1425 # write your own query generator and add it here: 1426 # use compile() for speedup 1427 # must escape strings before use !! 1428 # ORDER BY ! 1429 # FIXME: what about "< 40" ? 1430 #--------------------------------------------------------
1431 - def _generate_simple_query(self, raw):
1432 """Compose queries if search term seems unambigous.""" 1433 queries = [] 1434 1435 # "<digits>" - GNUmed patient PK or DOB 1436 if regex.match(u"^(\s|\t)*\d+(\s|\t)*$", raw, flags = regex.LOCALE | regex.UNICODE): 1437 _log.debug("[%s]: a PK or DOB" % raw) 1438 tmp = raw.strip() 1439 queries.append ({ 1440 'cmd': u"select *, %s::text as match_type FROM dem.v_basic_person WHERE pk_identity = %s order by lastnames, firstnames, dob", 1441 'args': [_('internal patient ID'), tmp] 1442 }) 1443 queries.append ({ 1444 'cmd': u"SELECT *, %s::text as match_type FROM dem.v_basic_person WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) order by lastnames, firstnames, dob", 1445 'args': [_('date of birth'), tmp.replace(',', '.')] 1446 }) 1447 queries.append ({ 1448 'cmd': u""" 1449 select vba.*, %s::text as match_type from dem.lnk_identity2ext_id li2ext_id, dem.v_basic_person vba 1450 where vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s) 1451 order by lastnames, firstnames, dob""", 1452 'args': [_('external patient ID'), tmp] 1453 }) 1454 return queries 1455 1456 # "<d igi ts>" - DOB or patient PK 1457 if regex.match(u"^(\d|\s|\t)+$", raw, flags = regex.LOCALE | regex.UNICODE): 1458 _log.debug("[%s]: a DOB or PK" % raw) 1459 queries.append ({ 1460 'cmd': u"SELECT *, %s::text as match_type FROM dem.v_basic_person WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) order by lastnames, firstnames, dob", 1461 'args': [_('date of birth'), raw.replace(',', '.')] 1462 }) 1463 tmp = raw.replace(u' ', u'') 1464 tmp = tmp.replace(u'\t', u'') 1465 queries.append ({ 1466 'cmd': u"SELECT *, %s::text as match_type from dem.v_basic_person WHERE pk_identity LIKE %s%%", 1467 'args': [_('internal patient ID'), tmp] 1468 }) 1469 return queries 1470 1471 # "#<di git s>" - GNUmed patient PK 1472 if regex.match(u"^(\s|\t)*#(\d|\s|\t)+$", raw, flags = regex.LOCALE | regex.UNICODE): 1473 _log.debug("[%s]: a PK or external ID" % raw) 1474 tmp = raw.replace(u'#', u'') 1475 tmp = tmp.strip() 1476 tmp = tmp.replace(u' ', u'') 1477 tmp = tmp.replace(u'\t', u'') 1478 # this seemingly stupid query ensures the PK actually exists 1479 queries.append ({ 1480 'cmd': u"SELECT *, %s::text as match_type from dem.v_basic_person WHERE pk_identity = %s order by lastnames, firstnames, dob", 1481 'args': [_('internal patient ID'), tmp] 1482 }) 1483 # but might also be an external ID 1484 tmp = raw.replace(u'#', u'') 1485 tmp = tmp.strip() 1486 tmp = tmp.replace(u' ', u'***DUMMY***') 1487 tmp = tmp.replace(u'\t', u'***DUMMY***') 1488 tmp = tmp.replace(u'***DUMMY***', u'(\s|\t|-|/)*') 1489 queries.append ({ 1490 'cmd': u""" 1491 select vba.*, %s::text as match_type from dem.lnk_identity2ext_id li2ext_id, dem.v_basic_person vba 1492 where vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s) 1493 order by lastnames, firstnames, dob""", 1494 'args': [_('external patient ID'), tmp] 1495 }) 1496 return queries 1497 1498 # "#<di/git s or c-hars>" - external ID (or PUPIC) 1499 if regex.match(u"^(\s|\t)*#.+$", raw, flags = regex.LOCALE | regex.UNICODE): 1500 _log.debug("[%s]: an external ID" % raw) 1501 tmp = raw.replace(u'#', u'') 1502 tmp = tmp.strip() 1503 tmp = tmp.replace(u' ', u'***DUMMY***') 1504 tmp = tmp.replace(u'\t', u'***DUMMY***') 1505 tmp = tmp.replace(u'-', u'***DUMMY***') 1506 tmp = tmp.replace(u'/', u'***DUMMY***') 1507 tmp = tmp.replace(u'***DUMMY***', u'(\s|\t|-|/)*') 1508 queries.append ({ 1509 'cmd': u""" 1510 select vba.*, %s::text as match_type from dem.lnk_identity2ext_id li2ext_id, dem.v_basic_person vba 1511 where vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s) 1512 order by lastnames, firstnames, dob""", 1513 'args': [_('external patient ID'), tmp] 1514 }) 1515 return queries 1516 1517 # digits interspersed with "./-" or blank space - DOB 1518 if regex.match(u"^(\s|\t)*\d+(\s|\t|\.|\-|/)*\d+(\s|\t|\.|\-|/)*\d+(\s|\t|\.)*$", raw, flags = regex.LOCALE | regex.UNICODE): 1519 _log.debug("[%s]: a DOB" % raw) 1520 tmp = raw.strip() 1521 while u'\t\t' in tmp: tmp = tmp.replace(u'\t\t', u' ') 1522 while u' ' in tmp: tmp = tmp.replace(u' ', u' ') 1523 # apparently not needed due to PostgreSQL smarts... 1524 #tmp = tmp.replace('-', '.') 1525 #tmp = tmp.replace('/', '.') 1526 queries.append ({ 1527 'cmd': u"SELECT *, %s as match_type from dem.v_basic_person WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) order by lastnames, firstnames, dob", 1528 'args': [_('date of birth'), tmp.replace(',', '.')] 1529 }) 1530 return queries 1531 1532 # " , <alpha>" - first name 1533 if regex.match(u"^(\s|\t)*,(\s|\t)*([^0-9])+(\s|\t)*$", raw, flags = regex.LOCALE | regex.UNICODE): 1534 _log.debug("[%s]: a firstname" % raw) 1535 tmp = self._normalize_soundalikes(raw[1:].strip()) 1536 cmd = u""" 1537 SELECT DISTINCT ON (pk_identity) * from ( 1538 select *, %s as match_type from (( 1539 select vbp.* 1540 FROM dem.names, dem.v_basic_person vbp 1541 WHERE dem.names.firstnames ~ %s and vbp.pk_identity = dem.names.id_identity 1542 ) union all ( 1543 select vbp.* 1544 FROM dem.names, dem.v_basic_person vbp 1545 WHERE dem.names.firstnames ~ %s and vbp.pk_identity = dem.names.id_identity 1546 )) as super_list order by lastnames, firstnames, dob 1547 ) as sorted_list""" 1548 queries.append ({ 1549 'cmd': cmd, 1550 'args': [_('first name'), '^' + gmTools.capitalize(tmp, mode=gmTools.CAPS_NAMES), '^' + tmp] 1551 }) 1552 return queries 1553 1554 # "*|$<...>" - DOB 1555 if regex.match(u"^(\s|\t)*(\*|\$).+$", raw, flags = regex.LOCALE | regex.UNICODE): 1556 _log.debug("[%s]: a DOB" % raw) 1557 tmp = raw.replace(u'*', u'') 1558 tmp = tmp.replace(u'$', u'') 1559 queries.append ({ 1560 'cmd': u"SELECT *, %s as match_type from dem.v_basic_person WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) order by lastnames, firstnames, dob", 1561 'args': [_('date of birth'), tmp.replace(u',', u'.')] 1562 }) 1563 return queries 1564 1565 return queries # = []
1566 #-------------------------------------------------------- 1567 # generic, locale independant queries 1568 #--------------------------------------------------------
1569 - def _generate_queries_from_dto(self, dto = None):
1570 """Generate generic queries. 1571 1572 - not locale dependant 1573 - data -> firstnames, lastnames, dob, gender 1574 """ 1575 _log.debug(u'_generate_queries_from_dto("%s")' % dto) 1576 1577 if not isinstance(dto, cDTO_person): 1578 return None 1579 1580 vals = [_('name, gender, date of birth')] 1581 where_snippets = [] 1582 1583 vals.append(dto.firstnames) 1584 where_snippets.append(u'firstnames=%s') 1585 vals.append(dto.lastnames) 1586 where_snippets.append(u'lastnames=%s') 1587 1588 if dto.dob is not None: 1589 vals.append(dto.dob) 1590 #where_snippets.append(u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)") 1591 where_snippets.append(u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s)") 1592 1593 if dto.gender is not None: 1594 vals.append(dto.gender) 1595 where_snippets.append('gender=%s') 1596 1597 # sufficient data ? 1598 if len(where_snippets) == 0: 1599 _log.error('invalid search dict structure') 1600 _log.debug(data) 1601 return None 1602 1603 cmd = u""" 1604 select *, %%s as match_type from dem.v_basic_person 1605 where pk_identity in ( 1606 select id_identity from dem.names where %s 1607 ) order by lastnames, firstnames, dob""" % ' and '.join(where_snippets) 1608 1609 queries = [ 1610 {'cmd': cmd, 'args': vals} 1611 ] 1612 1613 # shall we mogrify name parts ? probably not 1614 1615 return queries
1616 #-------------------------------------------------------- 1617 # queries for DE 1618 #--------------------------------------------------------
1619 - def _generate_queries_de(self, search_term = None):
1620 1621 if search_term is None: 1622 return [] 1623 1624 # check to see if we get away with a simple query ... 1625 queries = self._generate_simple_query(search_term) 1626 if len(queries) > 0: 1627 return queries 1628 1629 _log.debug('[%s]: not a search term with a "suggestive" structure' % search_term) 1630 1631 # no we don't 1632 queries = [] 1633 1634 # replace Umlauts 1635 normalized = self._normalize_soundalikes(search_term) 1636 1637 # "<CHARS>" - single name part 1638 # yes, I know, this is culture specific (did you read the docs ?) 1639 if regex.match(u"^(\s|\t)*[a-zäöüßéáúóçøA-ZÄÖÜÇØ]+(\s|\t)*$", search_term, flags = regex.LOCALE | regex.UNICODE): 1640 # there's no intermediate whitespace due to the regex 1641 cmd = u""" 1642 SELECT DISTINCT ON (pk_identity) * from ( 1643 select * from (( 1644 select vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.lastnames) ~* lower(%s) 1645 ) union all ( 1646 -- first name 1647 select vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) 1648 ) union all ( 1649 -- anywhere in name 1650 select vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames || n.lastnames || coalesce(n.preferred, '')) ~* lower(%s) 1651 )) as super_list order by lastnames, firstnames, dob 1652 ) as sorted_list""" 1653 tmp = normalized.strip() 1654 args = [] 1655 args.append(_('last name')) 1656 args.append('^' + tmp) 1657 args.append(_('first name')) 1658 args.append('^' + tmp) 1659 args.append(_('any name part')) 1660 args.append(tmp) 1661 1662 queries.append ({ 1663 'cmd': cmd, 1664 'args': args 1665 }) 1666 return queries 1667 1668 # try to split on (major) part separators 1669 parts_list = regex.split(u",|;", normalized) 1670 1671 # only one "major" part ? (i.e. no ",;" ?) 1672 if len(parts_list) == 1: 1673 # re-split on whitespace 1674 sub_parts_list = regex.split(u"\s*|\t*", normalized) 1675 1676 # parse into name/date parts 1677 date_count = 0 1678 name_parts = [] 1679 for part in sub_parts_list: 1680 # any digit signifies a date 1681 # FIXME: what about "<40" ? 1682 if regex.search(u"\d", part, flags = regex.LOCALE | regex.UNICODE): 1683 date_count = date_count + 1 1684 date_part = part 1685 else: 1686 name_parts.append(part) 1687 1688 # exactly 2 words ? 1689 if len(sub_parts_list) == 2: 1690 # no date = "first last" or "last first" 1691 if date_count == 0: 1692 # assumption: first last 1693 queries.append ({ 1694 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and n.firstnames ~ %s AND n.lastnames ~ %s", 1695 'args': [_('name: first-last'), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES)] 1696 }) 1697 queries.append ({ 1698 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s)", 1699 'args': [_('name: first-last'), '^' + name_parts[0], '^' + name_parts[1]] 1700 }) 1701 # assumption: last first 1702 queries.append ({ 1703 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and n.firstnames ~ %s AND n.lastnames ~ %s", 1704 'args': [_('name: last-first'), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES)] 1705 }) 1706 queries.append ({ 1707 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s)", 1708 'args': [_('name: last-first'), '^' + name_parts[1], '^' + name_parts[0]] 1709 }) 1710 # name parts anywhere in name - third order query ... 1711 queries.append ({ 1712 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames || n.lastnames) ~* lower(%s) AND lower(n.firstnames || n.lastnames) ~* lower(%s)", 1713 'args': [_('name'), name_parts[0], name_parts[1]] 1714 }) 1715 return queries 1716 # FIXME: either "name date" or "date date" 1717 _log.error("don't know how to generate queries for [%s]" % search_term) 1718 return queries 1719 1720 # exactly 3 words ? 1721 if len(sub_parts_list) == 3: 1722 # special case: 3 words, exactly 1 of them a date, no ",;" 1723 if date_count == 1: 1724 # assumption: first, last, dob - first order 1725 queries.append ({ 1726 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and n.firstnames ~ %s AND n.lastnames ~ %s AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 1727 'args': [_('names: first-last, date of birth'), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES), date_part.replace(u',', u'.')] 1728 }) 1729 queries.append ({ 1730 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s) AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 1731 'args': [_('names: first-last, date of birth'), '^' + name_parts[0], '^' + name_parts[1], date_part.replace(u',', u'.')] 1732 }) 1733 # assumption: last, first, dob - second order query 1734 queries.append ({ 1735 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and n.firstnames ~ %s AND n.lastnames ~ %s AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 1736 'args': [_('names: last-first, date of birth'), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES), date_part.replace(u',', u'.')] 1737 }) 1738 queries.append ({ 1739 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s) AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 1740 'args': [_('names: last-first, dob'), '^' + name_parts[1], '^' + name_parts[0], date_part.replace(u',', u'.')] 1741 }) 1742 # name parts anywhere in name - third order query ... 1743 queries.append ({ 1744 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames || n.lastnames) ~* lower(%s) AND lower(n.firstnames || n.lastnames) ~* lower(%s) AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 1745 'args': [_('name, date of birth'), name_parts[0], name_parts[1], date_part.replace(u',', u'.')] 1746 }) 1747 return queries 1748 # FIXME: "name name name" or "name date date" 1749 queries.append(self._generate_dumb_brute_query(search_term)) 1750 return queries 1751 1752 # FIXME: no ',;' but neither "name name" nor "name name date" 1753 queries.append(self._generate_dumb_brute_query(search_term)) 1754 return queries 1755 1756 # more than one major part (separated by ';,') 1757 else: 1758 # parse into name and date parts 1759 date_parts = [] 1760 name_parts = [] 1761 name_count = 0 1762 for part in parts_list: 1763 # any digits ? 1764 if regex.search(u"\d+", part, flags = regex.LOCALE | regex.UNICODE): 1765 # FIXME: parse out whitespace *not* adjacent to a *word* 1766 date_parts.append(part) 1767 else: 1768 tmp = part.strip() 1769 tmp = regex.split(u"\s*|\t*", tmp) 1770 name_count = name_count + len(tmp) 1771 name_parts.append(tmp) 1772 1773 where_parts = [] 1774 # first, handle name parts 1775 # special case: "<date(s)>, <name> <name>, <date(s)>" 1776 if (len(name_parts) == 1) and (name_count == 2): 1777 # usually "first last" 1778 where_parts.append ({ 1779 'conditions': u"firstnames ~ %s and lastnames ~ %s", 1780 'args': [_('names: first last'), '^' + gmTools.capitalize(name_parts[0][0], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0][1], mode=gmTools.CAPS_NAMES)] 1781 }) 1782 where_parts.append ({ 1783 'conditions': u"lower(firstnames) ~* lower(%s) and lower(lastnames) ~* lower(%s)", 1784 'args': [_('names: first last'), '^' + name_parts[0][0], '^' + name_parts[0][1]] 1785 }) 1786 # but sometimes "last first"" 1787 where_parts.append ({ 1788 'conditions': u"firstnames ~ %s and lastnames ~ %s", 1789 'args': [_('names: last, first'), '^' + gmTools.capitalize(name_parts[0][1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0][0], mode=gmTools.CAPS_NAMES)] 1790 }) 1791 where_parts.append ({ 1792 'conditions': u"lower(firstnames) ~* lower(%s) and lower(lastnames) ~* lower(%s)", 1793 'args': [_('names: last, first'), '^' + name_parts[0][1], '^' + name_parts[0][0]] 1794 }) 1795 # or even substrings anywhere in name 1796 where_parts.append ({ 1797 'conditions': u"lower(firstnames || lastnames) ~* lower(%s) OR lower(firstnames || lastnames) ~* lower(%s)", 1798 'args': [_('name'), name_parts[0][0], name_parts[0][1]] 1799 }) 1800 1801 # special case: "<date(s)>, <name(s)>, <name(s)>, <date(s)>" 1802 elif len(name_parts) == 2: 1803 # usually "last, first" 1804 where_parts.append ({ 1805 'conditions': u"firstnames ~ %s AND lastnames ~ %s", 1806 'args': [_('name: last, first'), '^' + ' '.join(map(gmTools.capitalize, name_parts[1])), '^' + ' '.join(map(gmTools.capitalize, name_parts[0]))] 1807 }) 1808 where_parts.append ({ 1809 'conditions': u"lower(firstnames) ~* lower(%s) AND lower(lastnames) ~* lower(%s)", 1810 'args': [_('name: last, first'), '^' + ' '.join(name_parts[1]), '^' + ' '.join(name_parts[0])] 1811 }) 1812 # but sometimes "first, last" 1813 where_parts.append ({ 1814 'conditions': u"firstnames ~ %s AND lastnames ~ %s", 1815 'args': [_('name: last, first'), '^' + ' '.join(map(gmTools.capitalize, name_parts[0])), '^' + ' '.join(map(gmTools.capitalize, name_parts[1]))] 1816 }) 1817 where_parts.append ({ 1818 'conditions': u"lower(firstnames) ~* lower(%s) AND lower(lastnames) ~* lower(%s)", 1819 'args': [_('name: last, first'), '^' + ' '.join(name_parts[0]), '^' + ' '.join(name_parts[1])] 1820 }) 1821 # or even substrings anywhere in name 1822 where_parts.append ({ 1823 'conditions': u"lower(firstnames || lastnames) ~* lower(%s) AND lower(firstnames || lastnames) ~* lower(%s)", 1824 'args': [_('name'), ' '.join(name_parts[0]), ' '.join(name_parts[1])] 1825 }) 1826 1827 # big trouble - arbitrary number of names 1828 else: 1829 # FIXME: deep magic, not sure of rationale ... 1830 if len(name_parts) == 1: 1831 for part in name_parts[0]: 1832 where_parts.append ({ 1833 'conditions': u"lower(firstnames || lastnames) ~* lower(%s)", 1834 'args': [_('name'), part] 1835 }) 1836 else: 1837 tmp = [] 1838 for part in name_parts: 1839 tmp.append(' '.join(part)) 1840 for part in tmp: 1841 where_parts.append ({ 1842 'conditions': u"lower(firstnames || lastnames) ~* lower(%s)", 1843 'args': [_('name'), part] 1844 }) 1845 1846 # secondly handle date parts 1847 # FIXME: this needs a considerable smart-up ! 1848 if len(date_parts) == 1: 1849 if len(where_parts) == 0: 1850 where_parts.append ({ 1851 'conditions': u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 1852 'args': [_('date of birth'), date_parts[0].replace(u',', u'.')] 1853 }) 1854 if len(where_parts) > 0: 1855 where_parts[0]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)" 1856 where_parts[0]['args'].append(date_parts[0].replace(u',', u'.')) 1857 where_parts[0]['args'][0] += u', ' + _('date of birth') 1858 if len(where_parts) > 1: 1859 where_parts[1]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)" 1860 where_parts[1]['args'].append(date_parts[0].replace(u',', u'.')) 1861 where_parts[1]['args'][0] += u', ' + _('date of birth') 1862 elif len(date_parts) > 1: 1863 if len(where_parts) == 0: 1864 where_parts.append ({ 1865 'conditions': u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp witih time zone) AND dem.date_trunc_utc('day'::text, dem.identity.deceased) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 1866 'args': [_('date of birth/death'), date_parts[0].replace(u',', u'.'), date_parts[1].replace(u',', u'.')] 1867 }) 1868 if len(where_parts) > 0: 1869 where_parts[0]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) AND dem.date_trunc_utc('day'::text, dem.identity.deceased) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 1870 where_parts[0]['args'].append(date_parts[0].replace(u',', u'.'), date_parts[1].replace(u',', u'.')) 1871 where_parts[0]['args'][0] += u', ' + _('date of birth/death') 1872 if len(where_parts) > 1: 1873 where_parts[1]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) AND dem.date_trunc_utc('day'::text, dem.identity.deceased) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 1874 where_parts[1]['args'].append(date_parts[0].replace(u',', u'.'), date_parts[1].replace(u',', u'.')) 1875 where_parts[1]['args'][0] += u', ' + _('date of birth/death') 1876 1877 # and finally generate the queries ... 1878 for where_part in where_parts: 1879 queries.append ({ 1880 'cmd': u"select *, %%s::text as match_type from dem.v_basic_person where %s" % where_part['conditions'], 1881 'args': where_part['args'] 1882 }) 1883 return queries 1884 1885 return []
1886 #--------------------------------------------------------
1887 - def _generate_dumb_brute_query(self, search_term=''):
1888 1889 _log.debug('_generate_dumb_brute_query("%s")' % search_term) 1890 1891 where_clause = '' 1892 args = [] 1893 # FIXME: split on more than just ' ' 1894 for arg in search_term.strip().split(): 1895 where_clause += u' and lower(vbp.title || vbp.firstnames || vbp.lastnames) ~* lower(%s)' 1896 args.append(arg) 1897 1898 query = u""" 1899 select distinct on (pk_identity) * from ( 1900 select 1901 vbp.*, '%s'::text as match_type 1902 from 1903 dem.v_basic_person vbp, 1904 dem.names n 1905 where 1906 vbp.pk_identity = n.id_identity 1907 %s 1908 order by 1909 lastnames, 1910 firstnames, 1911 dob 1912 ) as ordered_list""" % (_('full name'), where_clause) 1913 1914 return ({'cmd': query, 'args': args})
1915 #============================================================ 1916 # match providers 1917 #============================================================
1918 -class cMatchProvider_Provider(gmMatchProvider.cMatchProvider_SQL2):
1919 - def __init__(self):
1920 gmMatchProvider.cMatchProvider_SQL2.__init__( 1921 self, 1922 queries = [ 1923 u"""select 1924 pk_staff, 1925 short_alias || ' (' || coalesce(title, '') || firstnames || ' ' || lastnames || ')', 1926 1 1927 from dem.v_staff 1928 where 1929 is_active and ( 1930 short_alias %(fragment_condition)s or 1931 firstnames %(fragment_condition)s or 1932 lastnames %(fragment_condition)s or 1933 db_user %(fragment_condition)s 1934 )""" 1935 ] 1936 ) 1937 self.setThresholds(1, 2, 3)
1938 #============================================================ 1939 # convenience functions 1940 #============================================================
1941 -def create_name(pk_person, firstnames, lastnames, active=False):
1942 queries = [{ 1943 'cmd': u"select dem.add_name(%s, %s, %s, %s)", 1944 'args': [pk_person, firstnames, lastnames, active] 1945 }] 1946 rows, idx = gmPG2.run_rw_queries(queries=queries, return_data=True) 1947 name = cPersonName(aPK_obj = rows[0][0]) 1948 return name
1949 #============================================================
1950 -def create_identity(gender=None, dob=None, lastnames=None, firstnames=None):
1951 1952 cmd1 = u"""insert into dem.identity (gender, dob) values (%s, %s)""" 1953 1954 cmd2 = u""" 1955 insert into dem.names ( 1956 id_identity, lastnames, firstnames 1957 ) values ( 1958 currval('dem.identity_pk_seq'), coalesce(%s, 'xxxDEFAULTxxx'), coalesce(%s, 'xxxDEFAULTxxx') 1959 )""" 1960 1961 rows, idx = gmPG2.run_rw_queries ( 1962 queries = [ 1963 {'cmd': cmd1, 'args': [gender, dob]}, 1964 {'cmd': cmd2, 'args': [lastnames, firstnames]}, 1965 {'cmd': u"select currval('dem.identity_pk_seq')"} 1966 ], 1967 return_data = True 1968 ) 1969 return cIdentity(aPK_obj=rows[0][0])
1970 #============================================================
1971 -def create_dummy_identity():
1972 cmd1 = u"insert into dem.identity(gender) values('xxxDEFAULTxxx')" 1973 cmd2 = u"select currval('dem.identity_pk_seq')" 1974 1975 rows, idx = gmPG2.run_rw_queries ( 1976 queries = [ 1977 {'cmd': cmd1}, 1978 {'cmd': cmd2} 1979 ], 1980 return_data = True 1981 ) 1982 return gmDemographicRecord.cIdentity(aPK_obj = rows[0][0])
1983 #============================================================
1984 -def set_active_patient(patient=None, forced_reload=False):
1985 """Set active patient. 1986 1987 If patient is -1 the active patient will be UNset. 1988 """ 1989 if isinstance(patient, cPatient): 1990 pat = patient 1991 elif isinstance(patient, cIdentity): 1992 pat = cPatient(aPK_obj=patient['pk_identity']) 1993 elif isinstance(patient, cStaff): 1994 pat = cPatient(aPK_obj=patient['pk_identity']) 1995 elif isinstance(patient, gmCurrentPatient): 1996 pat = patient.patient 1997 elif patient == -1: 1998 pat = patient 1999 else: 2000 raise ValueError('<patient> must be either -1, cPatient, cStaff, cIdentity or gmCurrentPatient instance, is: %s' % patient) 2001 2002 # attempt to switch 2003 try: 2004 gmCurrentPatient(patient = pat, forced_reload = forced_reload) 2005 except: 2006 _log.exception('error changing active patient to [%s]' % patient) 2007 return False 2008 2009 return True
2010 #------------------------------------------------------------
2011 -def prompted_input(prompt, default=None):
2012 """Obtains entry from standard input. 2013 2014 prompt - Prompt text to display in standard output 2015 default - Default value (for user to press enter only) 2016 """ 2017 msg = '%s (CTRL-C aborts) [%s]: ' % (prompt, default) 2018 try: 2019 usr_input = raw_input(msg) 2020 except KeyboardInterrupt: 2021 return None 2022 if usr_input == '': 2023 return default 2024 return usr_input
2025 #------------------------------------------------------------
2026 -def ask_for_patient():
2027 """Text mode UI function to ask for patient.""" 2028 2029 person_searcher = cPatientSearcher_SQL() 2030 2031 while True: 2032 search_fragment = prompted_input("\nEnter person search term or leave blank to exit") 2033 2034 if search_fragment in ['exit', 'quit', 'bye', None]: 2035 print "user cancelled patient search" 2036 return None 2037 2038 pats = person_searcher.get_patients(search_term = search_fragment) 2039 2040 if (pats is None) or (len(pats) == 0): 2041 print "No patient matches the query term." 2042 print "" 2043 continue 2044 2045 if len(pats) > 1: 2046 print "Several patients match the query term:" 2047 print "" 2048 for pat in pats: 2049 print pat 2050 print "" 2051 continue 2052 2053 return pats[0] 2054 2055 return None
2056 #============================================================ 2057 # gender related 2058 #------------------------------------------------------------
2059 -def get_gender_list():
2060 """Retrieves the list of known genders from the database.""" 2061 global __gender_idx 2062 global __gender_list 2063 2064 if __gender_list is None: 2065 cmd = u"select tag, l10n_tag, label, l10n_label, sort_weight from dem.v_gender_labels order by sort_weight desc" 2066 __gender_list, __gender_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 2067 2068 return (__gender_list, __gender_idx)
2069 #------------------------------------------------------------ 2070 map_gender2mf = { 2071 'm': u'm', 2072 'f': u'f', 2073 'tf': u'f', 2074 'tm': u'm', 2075 'h': u'mf' 2076 } 2077 #------------------------------------------------------------ 2078 # Maps GNUmed related i18n-aware gender specifiers to a unicode symbol. 2079 map_gender2symbol = { 2080 'm': u'\u2642', 2081 'f': u'\u2640', 2082 'tf': u'\u26A5\u2640', 2083 'tm': u'\u26A5\u2642', 2084 'h': u'\u26A5' 2085 # 'tf': u'\u2642\u2640-\u2640', 2086 # 'tm': u'\u2642\u2640-\u2642', 2087 # 'h': u'\u2642\u2640' 2088 } 2089 #------------------------------------------------------------
2090 -def map_gender2salutation(gender=None):
2091 """Maps GNUmed related i18n-aware gender specifiers to a human-readable salutation.""" 2092 2093 global __gender2salutation_map 2094 2095 if __gender2salutation_map is None: 2096 genders, idx = get_gender_list() 2097 __gender2salutation_map = { 2098 'm': _('Mr'), 2099 'f': _('Mrs'), 2100 'tf': u'', 2101 'tm': u'', 2102 'h': u'' 2103 } 2104 for g in genders: 2105 __gender2salutation_map[g[idx['l10n_tag']]] = __gender2salutation_map[g[idx['tag']]] 2106 __gender2salutation_map[g[idx['label']]] = __gender2salutation_map[g[idx['tag']]] 2107 __gender2salutation_map[g[idx['l10n_label']]] = __gender2salutation_map[g[idx['tag']]] 2108 2109 return __gender2salutation_map[gender]
2110 #------------------------------------------------------------
2111 -def map_firstnames2gender(firstnames=None):
2112 """Try getting the gender for the given first name.""" 2113 2114 if firstnames is None: 2115 return None 2116 2117 rows, idx = gmPG2.run_ro_queries(queries = [{ 2118 'cmd': u"select gender from dem.name_gender_map where name ilike %(fn)s limit 1", 2119 'args': {'fn': firstnames} 2120 }]) 2121 2122 if len(rows) == 0: 2123 return None 2124 2125 return rows[0][0]
2126 #============================================================
2127 -def get_staff_list(active_only=False):
2128 if active_only: 2129 cmd = u"select * from dem.v_staff where is_active order by can_login desc, short_alias asc" 2130 else: 2131 cmd = u"select * from dem.v_staff order by can_login desc, is_active desc, short_alias asc" 2132 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True) 2133 staff_list = [] 2134 for row in rows: 2135 obj_row = { 2136 'idx': idx, 2137 'data': row, 2138 'pk_field': 'pk_staff' 2139 } 2140 staff_list.append(cStaff(row=obj_row)) 2141 return staff_list
2142 #============================================================
2143 -def get_persons_from_pks(pks=None):
2144 return [ cIdentity(aPK_obj = pk) for pk in pks ]
2145 #============================================================
2146 -def get_person_from_xdt(filename=None, encoding=None, dob_format=None):
2147 from Gnumed.business import gmXdtObjects 2148 return gmXdtObjects.read_person_from_xdt(filename=filename, encoding=encoding, dob_format=dob_format)
2149 #============================================================
2150 -def get_persons_from_pracsoft_file(filename=None, encoding='ascii'):
2151 from Gnumed.business import gmPracSoftAU 2152 return gmPracSoftAU.read_persons_from_pracsoft_file(filename=filename, encoding=encoding)
2153 #============================================================ 2154 # main/testing 2155 #============================================================ 2156 if __name__ == '__main__': 2157 2158 import datetime 2159 2160 gmI18N.activate_locale() 2161 gmI18N.install_domain() 2162 gmDateTime.init() 2163 2164 #--------------------------------------------------------
2165 - def test_set_active_pat():
2166 2167 ident = cIdentity(1) 2168 print "setting active patient with", ident 2169 set_active_patient(patient=ident) 2170 2171 patient = cPatient(12) 2172 print "setting active patient with", patient 2173 set_active_patient(patient=patient) 2174 2175 pat = gmCurrentPatient() 2176 print pat['dob'] 2177 #pat['dob'] = 'test' 2178 2179 staff = cStaff() 2180 print "setting active patient with", staff 2181 set_active_patient(patient=staff) 2182 2183 print "setting active patient with -1" 2184 set_active_patient(patient=-1)
2185 #--------------------------------------------------------
2186 - def test_dto_person():
2187 dto = cDTO_person() 2188 dto.firstnames = 'Sepp' 2189 dto.lastnames = 'Herberger' 2190 dto.gender = 'male' 2191 dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone) 2192 print dto 2193 2194 print dto['firstnames'] 2195 print dto['lastnames'] 2196 print dto['gender'] 2197 print dto['dob'] 2198 2199 for key in dto.keys(): 2200 print key
2201 #--------------------------------------------------------
2202 - def test_search_by_dto():
2203 dto = cDTO_person() 2204 dto.firstnames = 'Sigrid' 2205 dto.lastnames = 'Kiesewetter' 2206 dto.gender = 'female' 2207 # dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone) 2208 dto.dob = datetime.datetime(1939,6,24,23,0,0,0,gmDateTime.gmCurrentLocalTimezone) 2209 print dto 2210 2211 searcher = cPatientSearcher_SQL() 2212 pats = searcher.get_patients(dto = dto) 2213 print pats
2214 2215 #--------------------------------------------------------
2216 - def test_staff():
2217 staff = cStaff() 2218 print staff 2219 print staff.inbox 2220 print staff.inbox.messages
2221 2222 #--------------------------------------------------------
2223 - def test_current_provider():
2224 staff = cStaff() 2225 provider = gmCurrentProvider(provider = staff) 2226 print provider 2227 print provider.inbox 2228 print provider.inbox.messages 2229 print provider.database_language 2230 tmp = provider.database_language 2231 provider.database_language = None 2232 print provider.database_language 2233 provider.database_language = tmp 2234 print provider.database_language
2235 #--------------------------------------------------------
2236 - def test_identity():
2237 # create patient 2238 print '\n\nCreating identity...' 2239 new_identity = create_identity(gender='m', dob='2005-01-01', lastnames='test lastnames', firstnames='test firstnames') 2240 print 'Identity created: %s' % new_identity 2241 2242 print '\nSetting title and gender...' 2243 new_identity['title'] = 'test title'; 2244 new_identity['gender'] = 'f'; 2245 new_identity.save_payload() 2246 print 'Refetching identity from db: %s' % cIdentity(aPK_obj=new_identity['pk_identity']) 2247 2248 print '\nGetting all names...' 2249 for a_name in new_identity.get_names(): 2250 print a_name 2251 print 'Active name: %s' % (new_identity.get_active_name()) 2252 print 'Setting nickname...' 2253 new_identity.set_nickname(nickname='test nickname') 2254 print 'Refetching all names...' 2255 for a_name in new_identity.get_names(): 2256 print a_name 2257 print 'Active name: %s' % (new_identity.get_active_name()) 2258 2259 print '\nIdentity occupations: %s' % new_identity['occupations'] 2260 print 'Creating identity occupation...' 2261 new_identity.link_occupation('test occupation') 2262 print 'Identity occupations: %s' % new_identity['occupations'] 2263 2264 print '\nIdentity addresses: %s' % new_identity.get_addresses() 2265 print 'Creating identity address...' 2266 # make sure the state exists in the backend 2267 new_identity.link_address ( 2268 number = 'test 1234', 2269 street = 'test street', 2270 postcode = 'test postcode', 2271 urb = 'test urb', 2272 state = 'SN', 2273 country = 'DE' 2274 ) 2275 print 'Identity addresses: %s' % new_identity.get_addresses() 2276 2277 print '\nIdentity communications: %s' % new_identity.get_comm_channels() 2278 print 'Creating identity communication...' 2279 new_identity.link_comm_channel('homephone', '1234566') 2280 print 'Identity communications: %s' % new_identity.get_comm_channels()
2281 #--------------------------------------------------------
2282 - def test_patient_search_queries():
2283 searcher = cPatientSearcher_SQL() 2284 2285 print "testing _normalize_soundalikes()" 2286 print "--------------------------------" 2287 # FIXME: support Ähler -> Äler and Dähler -> Däler 2288 data = [u'Krüger', u'Krueger', u'Kruger', u'Überle', u'Böger', u'Boger', u'Öder', u'Ähler', u'Däler', u'Großer', u'müller', u'Özdemir', u'özdemir'] 2289 for name in data: 2290 print '%s: %s' % (name, searcher._normalize_soundalikes(name)) 2291 2292 raw_input('press [ENTER] to continue') 2293 print "============" 2294 2295 print "testing _generate_simple_query()" 2296 print "----------------------------" 2297 data = ['51234', '1 134 153', '#13 41 34', '#3-AFY322.4', '22-04-1906', '1235/32/3525', ' , johnny'] 2298 for fragment in data: 2299 print "fragment:", fragment 2300 qs = searcher._generate_simple_query(fragment) 2301 for q in qs: 2302 print " match on:", q['args'][0] 2303 print " query :", q['cmd'] 2304 raw_input('press [ENTER] to continue') 2305 print "============" 2306 2307 print "testing _generate_queries_from_dto()" 2308 print "------------------------------------" 2309 dto = cDTO_person() 2310 dto.gender = 'm' 2311 dto.lastnames = 'Kirk' 2312 dto.firstnames = 'James' 2313 dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone) 2314 q = searcher._generate_queries_from_dto(dto)[0] 2315 print "dto:", dto 2316 print " match on:", q['args'][0] 2317 print " query:", q['cmd'] 2318 2319 raw_input('press [ENTER] to continue') 2320 print "============" 2321 2322 print "testing _generate_queries_de()" 2323 print "------------------------------" 2324 qs = searcher._generate_queries_de('Kirk, James') 2325 for q in qs: 2326 print " match on:", q['args'][0] 2327 print " query :", q['cmd'] 2328 print " args :", q['args'] 2329 raw_input('press [ENTER] to continue') 2330 print "============" 2331 2332 qs = searcher._generate_queries_de(u'müller') 2333 for q in qs: 2334 print " match on:", q['args'][0] 2335 print " query :", q['cmd'] 2336 print " args :", q['args'] 2337 raw_input('press [ENTER] to continue') 2338 print "============" 2339 2340 qs = searcher._generate_queries_de(u'özdemir') 2341 for q in qs: 2342 print " match on:", q['args'][0] 2343 print " query :", q['cmd'] 2344 print " args :", q['args'] 2345 raw_input('press [ENTER] to continue') 2346 print "============" 2347 2348 qs = searcher._generate_queries_de(u'Özdemir') 2349 for q in qs: 2350 print " match on:", q['args'][0] 2351 print " query :", q['cmd'] 2352 print " args :", q['args'] 2353 raw_input('press [ENTER] to continue') 2354 print "============" 2355 2356 print "testing _generate_dumb_brute_query()" 2357 print "------------------------------------" 2358 q = searcher._generate_dumb_brute_query('Kirk, James Tiberius') 2359 print " match on:", q['args'][0] 2360 print " query:", q['cmd'] 2361 print " args:", q['args'] 2362 2363 raw_input('press [ENTER] to continue')
2364 #--------------------------------------------------------
2365 - def test_ask_for_patient():
2366 while 1: 2367 myPatient = ask_for_patient() 2368 if myPatient is None: 2369 break 2370 print "ID ", myPatient.ID 2371 print "names ", myPatient.get_names() 2372 print "addresses:", myPatient.get_addresses(address_type='home') 2373 print "recent birthday:", myPatient.dob_in_range() 2374 myPatient.export_as_gdt(filename='apw.gdt', encoding = 'cp850')
2375 # docs = myPatient.get_document_folder() 2376 # print "docs ", docs 2377 # emr = myPatient.get_emr() 2378 # print "EMR ", emr 2379 #--------------------------------------------------------
2380 - def test_name():
2381 for pk in range(1,16): 2382 name = cPersonName(aPK_obj=pk) 2383 print name.description 2384 print ' ', name
2385 #-------------------------------------------------------- 2386 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'): 2387 #test_patient_search_queries() 2388 #test_ask_for_patient() 2389 #test_dto_person() 2390 #test_identity() 2391 #test_set_active_pat() 2392 #test_search_by_dto() 2393 #test_staff() 2394 test_current_provider() 2395 #test_name() 2396 2397 #map_gender2salutation('m') 2398 2399 # module functions 2400 #genders, idx = get_gender_list() 2401 #print "\n\nRetrieving gender enum (tag, label, weight):" 2402 #for gender in genders: 2403 # print "%s, %s, %s" % (gender[idx['tag']], gender[idx['l10n_label']], gender[idx['sort_weight']]) 2404 2405 #comms = get_comm_list() 2406 #print "\n\nRetrieving communication media enum (id, description): %s" % comms 2407 2408 #============================================================ 2409 # $Log: gmPerson.py,v $ 2410 # Revision 1.198 2010-01-31 16:35:03 ncq 2411 # - put nick in "" 2412 # 2413 # Revision 1.197 2010/01/11 19:43:05 ncq 2414 # - do not change the patient if any of the 2415 # synchronous pre-selection callbacks fails 2416 # 2417 # Revision 1.196 2010/01/08 14:38:06 ncq 2418 # - support NULLing the dob 2419 # 2420 # Revision 1.195 2010/01/08 13:50:45 ncq 2421 # - enhance add-external-id() with pk-type 2422 # 2423 # Revision 1.194 2009/12/21 20:26:40 ncq 2424 # - some cleanup 2425 # 2426 # Revision 1.193 2009/12/21 14:59:17 ncq 2427 # - typo 2428 # 2429 # Revision 1.192 2009/11/30 22:24:16 ncq 2430 # - cleanup 2431 # 2432 # Revision 1.191 2009/11/13 21:04:12 ncq 2433 # - get-persons-from-pks 2434 # 2435 # Revision 1.190 2009/09/01 22:21:31 ncq 2436 # - nullify empty strings where appropriate 2437 # 2438 # Revision 1.189 2009/08/24 20:05:14 ncq 2439 # - use new cInboxMessage class 2440 # 2441 # Revision 1.188 2009/07/15 12:46:59 ncq 2442 # - support deceased 2443 # 2444 # Revision 1.187 2009/06/17 20:42:25 ncq 2445 # - is_patient property 2446 # 2447 # Revision 1.186 2009/06/04 16:24:13 ncq 2448 # - adjust to dob-less persons 2449 # 2450 # Revision 1.185 2009/04/24 12:04:44 ncq 2451 # - cleanup 2452 # 2453 # Revision 1.184 2009/04/21 16:54:04 ncq 2454 # - cleanup 2455 # 2456 # Revision 1.183 2009/04/03 09:32:01 ncq 2457 # - improved docs 2458 # 2459 # Revision 1.182 2009/02/25 21:05:36 ncq 2460 # - cleanup 2461 # 2462 # Revision 1.181 2009/02/25 09:49:49 ncq 2463 # - fix provider matcher to exlude inactive providers 2464 # 2465 # Revision 1.180 2009/02/20 15:42:08 ncq 2466 # - putting first patient on waiting list needs more care 2467 # 2468 # Revision 1.179 2009/02/10 18:37:36 ncq 2469 # - typo when deleting comm channel 2470 # 2471 # Revision 1.178 2009/01/30 12:08:20 ncq 2472 # - support zone in put_on_waiting_list 2473 # 2474 # Revision 1.177 2009/01/21 18:52:34 ncq 2475 # - signals cleanup 2476 # 2477 # Revision 1.176 2009/01/21 17:59:57 ncq 2478 # - improved logging 2479 # 2480 # Revision 1.175 2009/01/17 23:00:51 ncq 2481 # - put_on_waiting_list 2482 # 2483 # Revision 1.174 2009/01/02 11:36:18 ncq 2484 # - slightly reorder code for class dependancy clarity 2485 # - property database_language on staff 2486 # - raise AttributeError on faulty concurrent get_emr 2487 # 2488 # Revision 1.173 2008/12/25 16:52:41 ncq 2489 # - cleanup 2490 # - support .database_language on cStaff 2491 # 2492 # Revision 1.172 2008/12/22 18:58:02 ncq 2493 # - start supporting .tob 2494 # 2495 # Revision 1.171 2008/12/17 21:52:36 ncq 2496 # - add assimilation 2497 # 2498 # Revision 1.170 2008/12/09 23:19:47 ncq 2499 # - attribute description vs description_gender 2500 # 2501 # Revision 1.169 2008/11/23 12:42:57 ncq 2502 # - no more dummy names 2503 # 2504 # Revision 1.168 2008/11/21 13:03:36 ncq 2505 # - do not return a dummy name anymore 2506 # 2507 # Revision 1.167 2008/10/22 12:05:22 ncq 2508 # - improved logging of staff instantiation 2509 # 2510 # Revision 1.166 2008/10/12 15:15:07 ncq 2511 # - cleanup 2512 # - better exception wording 2513 # 2514 # Revision 1.165 2008/08/28 18:30:50 ncq 2515 # - cleanup 2516 # 2517 # Revision 1.164 2008/07/10 11:16:01 ncq 2518 # - make pre-selection callback failure more obvious 2519 # 2520 # Revision 1.163 2008/07/07 13:38:43 ncq 2521 # - is_connected -> connected property 2522 # - add in-sync pre-selection callbacks 2523 # 2524 # Revision 1.162 2008/06/28 18:24:24 ncq 2525 # - fix provider match provider to act on cursor-down / *, too 2526 # 2527 # Revision 1.161 2008/04/26 21:30:35 ncq 2528 # - fix unlink_occupation 2529 # 2530 # Revision 1.160 2008/04/02 10:15:17 ncq 2531 # - add missing s 2532 # 2533 # Revision 1.159 2008/03/09 20:13:47 ncq 2534 # - cleanup 2535 # 2536 # Revision 1.158 2008/02/25 17:31:41 ncq 2537 # - logging cleanup 2538 # 2539 # Revision 1.157 2008/01/30 13:34:50 ncq 2540 # - switch to std lib logging 2541 # 2542 # Revision 1.156 2008/01/27 21:08:32 ncq 2543 # - format_medical_age() improved 2544 # - map gender to unicode symbol 2545 # 2546 # Revision 1.155 2008/01/22 11:50:49 ncq 2547 # - cPersonName.description property aligned with cIdentity.get_description() 2548 # - test cPersonName 2549 # 2550 # Revision 1.154 2008/01/14 20:26:10 ncq 2551 # - better log 2552 # 2553 # Revision 1.153 2008/01/11 16:08:08 ncq 2554 # - first/last -> first-/lastnames 2555 # 2556 # Revision 1.152 2008/01/07 19:44:16 ncq 2557 # - use comm channel API 2558 # 2559 # Revision 1.151 2008/01/06 08:09:38 ncq 2560 # - in patient search by several means weed out duplicate finds 2561 # 2562 # Revision 1.150 2007/12/26 12:35:54 ncq 2563 # - cleanup 2564 # 2565 # Revision 1.149 2007/12/24 23:25:39 shilbert 2566 # - fix missing *args, **kwargs in import_extra_data 2567 # 2568 # Revision 1.148 2007/12/23 11:55:21 ncq 2569 # - cleanup 2570 # 2571 # Revision 1.147 2007/12/11 12:59:11 ncq 2572 # - cleanup and explicit signal handling 2573 # 2574 # Revision 1.146 2007/12/06 10:43:31 ncq 2575 # - fix typo 2576 # 2577 # Revision 1.145 2007/12/06 08:39:02 ncq 2578 # - add documentation on external IDs 2579 # - delete_external_id() 2580 # - edit_external_id() -> update_external_id() 2581 # 2582 # Revision 1.144 2007/12/03 20:42:37 ncq 2583 # - .delete_name() 2584 # - remove get_comm_list() 2585 # 2586 # Revision 1.143 2007/12/02 20:58:06 ncq 2587 # - adjust to table changes 2588 # - fix link_comm_channel() 2589 # 2590 # Revision 1.142 2007/11/28 22:35:03 ncq 2591 # - streamline get_description() 2592 # - make current patient listen on its identity/name tables 2593 # 2594 # Revision 1.141 2007/11/28 13:57:45 ncq 2595 # - fix SQL of cPersonName 2596 # 2597 # Revision 1.140 2007/11/28 11:51:48 ncq 2598 # - cPersonName 2599 # - cIdentity: 2600 # - check dob at __setitem__ level 2601 # - get_all_names() -> get_names(), remove cache 2602 # - use dem.v_person_names 2603 # - fix add_name() 2604 # - create_name() 2605 # 2606 # Revision 1.139 2007/11/17 16:11:42 ncq 2607 # - improve link_address() 2608 # - unlink_address() 2609 # 2610 # Revision 1.138 2007/11/12 22:56:34 ncq 2611 # - add missing '' around match_type 2612 # - get_external_ids() and use in export_as_gdt() 2613 # - add_external_id() 2614 # 2615 # Revision 1.137 2007/11/10 21:00:52 ncq 2616 # - implement dto.get_candidate_identities() and dto.import_into_database() 2617 # - stub dto.delete_from_source() 2618 # 2619 # Revision 1.136 2007/10/30 12:46:21 ncq 2620 # - test on "test" 2621 # 2622 # Revision 1.135 2007/10/30 12:43:42 ncq 2623 # - make inbox a property on cStaff 2624 # - teach gmCurrentProvider about __getattr__ 2625 # - improved testing 2626 # 2627 # Revision 1.134 2007/10/23 21:20:23 ncq 2628 # - cleanup 2629 # 2630 # Revision 1.133 2007/10/21 20:54:51 ncq 2631 # - add test case 2632 # 2633 # Revision 1.132 2007/10/09 11:22:05 ncq 2634 # - explicit casts for a whole bunch of queries 2635 # 2636 # Revision 1.131 2007/10/07 12:28:09 ncq 2637 # - workplace property now on gmSurgery.gmCurrentPractice() borg 2638 # 2639 # Revision 1.130 2007/09/24 22:08:56 ncq 2640 # - table-qualify ambigous column defs 2641 # 2642 # Revision 1.129 2007/09/10 12:34:02 ncq 2643 # - fix dob_in_range() 2644 # 2645 # Revision 1.128 2007/08/07 21:34:18 ncq 2646 # - cPaths -> gmPaths 2647 # 2648 # Revision 1.127 2007/07/17 21:43:29 ncq 2649 # - refcount patient lock 2650 # 2651 # Revision 1.126 2007/07/11 21:04:08 ncq 2652 # - make locked a property of gmCurrentPatient() 2653 # - improve ask_for_patient() 2654 # 2655 # Revision 1.125 2007/07/10 20:32:52 ncq 2656 # - return gmNull.cNull instance if gmCurrentProvider.patient is not connected 2657 # 2658 # Revision 1.124 2007/07/09 11:27:42 ncq 2659 # - put coalesce on dem.identity.title yet another time 2660 # 2661 # Revision 1.123 2007/06/28 12:31:34 ncq 2662 # - allow None for dob in dto 2663 # - set external ID to GNUmed interal ID on export_as_gdt() 2664 # - create proper queries from DTO in absence of, say, DOB 2665 # 2666 # Revision 1.122 2007/06/10 09:32:23 ncq 2667 # - cast "re" as "regex" 2668 # - use gmTools.capitalize() instead of homegrown _make_sane_caps() 2669 # - lots of u''ification in replace() 2670 # - improved query generation logging 2671 # - regex.match()/search() need u'' in the pattern or it 2672 # won't match anything in u'' strings, also set flags to 2673 # LOCALE/UNICODE 2674 # - use lower() on ~* queries since even PG 8.2 doesn't properly 2675 # support ~* with Umlauts :-(( 2676 # - improved test suite 2677 # 2678 # Revision 1.121 2007/05/21 22:29:18 ncq 2679 # - be more careful in link_occupation() 2680 # 2681 # Revision 1.120 2007/05/21 14:46:09 ncq 2682 # - cIdentity.get_dirname() 2683 # 2684 # Revision 1.119 2007/05/19 22:16:23 ncq 2685 # - a lot of cleanup/remomve _subtable stuff 2686 # - add __setitem__ to gmCurrentPatient 2687 # 2688 # Revision 1.118 2007/05/14 11:03:28 ncq 2689 # - latin1 -> utf8 2690 # 2691 # Revision 1.117 2007/05/11 14:10:52 ncq 2692 # - look in --conf-file for workplace def, too 2693 # 2694 # Revision 1.116 2007/05/07 12:29:02 ncq 2695 # - improve logic when looking for config file for workplace detection 2696 # 2697 # Revision 1.115 2007/05/07 08:00:18 ncq 2698 # - call get_emr() early enough 2699 # 2700 # Revision 1.114 2007/04/19 13:09:03 ncq 2701 # - read workplace from proper config file 2702 # 2703 # Revision 1.113 2007/04/06 23:14:24 ncq 2704 # - if we del the emr object link cache too early we'll get 2705 # a continue-encounter ? popup 2706 # 2707 # Revision 1.112 2007/03/18 13:04:42 ncq 2708 # - re-add lost 1.112 2709 # 2710 # Revision 1.112 2007/03/12 13:29:17 ncq 2711 # - add patient ID source in a smarter way 2712 # 2713 # Revision 1.111 2007/03/10 15:12:06 ncq 2714 # - export a dummy APW ID into the GDT file for demonstration 2715 # 2716 # Revision 1.110 2007/03/09 16:57:12 ncq 2717 # - prepare export_as_gdt() for use of pending-completion get_external_ids() 2718 # 2719 # Revision 1.109 2007/03/01 14:02:09 ncq 2720 # - support line length in export_as_gdt() :-( 2721 # 2722 # Revision 1.108 2007/02/22 22:38:56 ncq 2723 # - fix gdt field "names" 2724 # 2725 # Revision 1.107 2007/02/22 16:31:38 ncq 2726 # - u''ification 2727 # - massive cleanup/simplification 2728 # - cPatient/cStaff now cIdentity child 2729 # - remove cPerson 2730 # - make ID a property of cIdentity 2731 # - shadowing self._payload[self._idx['pk_identity']] 2732 # - so, no setting, only getting it, setting will raise Exception 2733 # - cIdentity.export_as_gdt() 2734 # - fix test suite so all tests pass again 2735 # 2736 # Revision 1.106 2007/02/19 16:45:21 ncq 2737 # - make DOB queries use dem.date_trunc_utc() 2738 # 2739 # Revision 1.105 2007/02/17 13:57:07 ncq 2740 # - cIdentity.dob_in_range() plus test 2741 # - make gmCurrentProvider.workplace an efficient property 2742 # 2743 # Revision 1.104 2007/02/15 14:56:53 ncq 2744 # - remove _() from ValueError() call 2745 # - map_firstnames2gender() 2746 # 2747 # Revision 1.103 2007/02/13 17:05:07 ncq 2748 # - add get_persons_from_pracsoft_file() 2749 # 2750 # Revision 1.102 2007/02/10 00:07:47 ncq 2751 # - ween out duplicate queries on getting patients 2752 # 2753 # Revision 1.101 2007/02/05 16:09:44 ncq 2754 # - fix person dto 2755 # 2756 # Revision 1.100 2007/01/16 17:58:11 ncq 2757 # -cleanup 2758 # 2759 # Revision 1.99 2007/01/16 14:23:24 ncq 2760 # - use current local time zone for now() in medical age calculation 2761 # 2762 # Revision 1.98 2007/01/16 12:08:29 ncq 2763 # - move dto.dob to datetime.datetime 2764 # 2765 # Revision 1.97 2007/01/15 13:01:19 ncq 2766 # - make dob queries cast dob literal to timestamp with time zone as it ought to be 2767 # - support dob_format in get_person_from_xdt() 2768 # 2769 # Revision 1.96 2006/12/13 13:43:10 ncq 2770 # - cleanup 2771 # 2772 # Revision 1.95 2006/11/27 12:37:09 ncq 2773 # - do not display 12y0m but rather 12y in format_age_medically() 2774 # 2775 # Revision 1.94 2006/11/24 09:33:22 ncq 2776 # - remove comms subtable 2777 # - chain cPerson.__getitem__ to underlying cIdentity where necessary 2778 # - fix no-cfg-file detection in get_workplace() 2779 # - add format_age_medically() and use it 2780 # 2781 # Revision 1.93 2006/11/20 19:10:39 ncq 2782 # - more consistent method names 2783 # - raise instead of return None where appropriate 2784 # - improved logging 2785 # - _generate_dumb_brute_query() returned wrong type 2786 # 2787 # Revision 1.92 2006/11/20 15:57:16 ncq 2788 # - fix (un)link_occupation when occupation/activies=None 2789 # - add get_comm_channels() 2790 # 2791 # Revision 1.91 2006/11/19 11:02:33 ncq 2792 # - remove subtable defs, add corresponding APIs 2793 # 2794 # Revision 1.90 2006/11/09 17:46:04 ncq 2795 # - raise exception if dob is about to be set without a timezone 2796 # 2797 # Revision 1.89 2006/11/07 23:43:34 ncq 2798 # - cIdentity now requires datetime.datetime as DOB 2799 # - fix dob2medical_age() 2800 # 2801 # Revision 1.88 2006/11/06 09:58:11 ncq 2802 # - add missing continue in get_identities() 2803 # 2804 # Revision 1.87 2006/11/05 16:01:24 ncq 2805 # - include nick in identity description string, user wants to 2806 # abuse it for other means 2807 # 2808 # Revision 1.86 2006/11/01 12:54:03 ncq 2809 # - return None from get_last_encounter() if there is none, that's the whole point ! 2810 # - fix patient search queries: select distinct on level above order by 2811 # so pk_identity does not have to be first order by parameter 2812 # 2813 # Revision 1.85 2006/10/31 11:26:56 ncq 2814 # - dob2medical_age(): use datetime.datetime 2815 # 2816 # Revision 1.84 2006/10/28 14:52:07 ncq 2817 # - add get_last_encounter() 2818 # 2819 # Revision 1.83 2006/10/24 13:16:38 ncq 2820 # - add Provider match provider 2821 # 2822 # Revision 1.82 2006/10/21 20:44:06 ncq 2823 # - no longer import gmPG 2824 # - convert to gmPG2 2825 # - add __gender2salutation_map, map_gender2salutation() 2826 # - adjust to changes in gmBusinessDBObject 2827 # - fix patient searcher query generation 2828 # - improved test suite 2829 # 2830 # Revision 1.81 2006/09/13 07:53:26 ncq 2831 # - in get_person_from_xdt() handle encoding 2832 # 2833 # Revision 1.80 2006/07/26 12:22:56 ncq 2834 # - improve set_active_patient 2835 # 2836 # Revision 1.79 2006/07/24 14:16:04 ncq 2837 # - cleanup 2838 # 2839 # Revision 1.78 2006/07/17 21:06:12 ncq 2840 # - cleanup 2841 # 2842 # Revision 1.77 2006/07/17 18:49:07 ncq 2843 # - fix wrong naming 2844 # 2845 # Revision 1.76 2006/07/17 18:08:03 ncq 2846 # - add cDTO_person() 2847 # - add get_patient_from_xdt() 2848 # - fix __generate_queries_generic() 2849 # - cleanup, better testing 2850 # 2851 # Revision 1.75 2006/06/15 07:54:04 ncq 2852 # - allow editing of db_user in cStaff except where cStaff represents CURRENT_USER 2853 # 2854 # Revision 1.74 2006/06/14 10:22:46 ncq 2855 # - create_* stored procs are in schema dem.* now 2856 # 2857 # Revision 1.73 2006/06/12 18:28:32 ncq 2858 # - added missing raise in gmCurrentPatient.__init__() 2859 # 2860 # Revision 1.72 2006/06/09 14:38:42 ncq 2861 # - sort result of get_staff_list() 2862 # 2863 # Revision 1.71 2006/06/06 20:47:39 ncq 2864 # - add is_active to staff class 2865 # - add get_staff_list() 2866 # 2867 # Revision 1.70 2006/05/25 22:12:50 ncq 2868 # - self._patient -> self.patient to be more pythonic 2869 # 2870 # Revision 1.69 2006/05/25 12:07:29 sjtan 2871 # 2872 # base class method needs self object. 2873 # 2874 # Revision 1.68 2006/05/15 13:24:13 ncq 2875 # - signal "activating_patient" -> "pre_patient_selection" 2876 # - signal "patient_selected" -> "post_patient_selection" 2877 # 2878 # Revision 1.67 2006/05/14 21:44:22 ncq 2879 # - add get_workplace() to gmPerson.gmCurrentProvider and make use thereof 2880 # - remove use of gmWhoAmI.py 2881 # 2882 # Revision 1.66 2006/05/12 13:53:08 ncq 2883 # - lazy import gmClinicalRecord 2884 # 2885 # Revision 1.65 2006/05/12 12:03:55 ncq 2886 # - gmLoggedOnStaffMember -> gmCurrentProvider 2887 # 2888 # Revision 1.64 2006/05/10 21:15:58 ncq 2889 # - add current provider Borg 2890 # - add cStaff 2891 # 2892 # Revision 1.63 2006/05/04 09:59:35 ncq 2893 # - add cStaffMember(cPerson) 2894 # 2895 # Revision 1.62 2006/05/04 09:41:05 ncq 2896 # - cPerson 2897 # - factor out stuff for cPatient 2898 # - self.__ID -> self._ID for inheritance 2899 # - cPatient 2900 # - inherit from cPerson 2901 # - add patient specific methods 2902 # - deprecate get_clinical_record() over get_emr() 2903 # - cleanup doc folder instance on cleanup() 2904 # - gmCurrentPatient 2905 # - keyword change person -> patient 2906 # - accept cPatient instance 2907 # - self._person -> self._patient 2908 # - cPatientSearcher_SQL 2909 # - add get_patients() 2910 # - set_active_patient() 2911 # - raise ValueError on such 2912 # - ask_for_patient() 2913 # - improve breakout detection 2914 # - remove side effect of activating patient 2915 # - make "unit tests" work again 2916 # 2917 # Revision 1.61 2006/01/11 13:14:20 ncq 2918 # - id -> pk 2919 # 2920 # Revision 1.60 2006/01/07 13:13:46 ncq 2921 # - more schema qualifications 2922 # 2923 # Revision 1.59 2006/01/06 10:15:37 ncq 2924 # - lots of small fixes adjusting to "dem" schema 2925 # 2926 # Revision 1.58 2005/11/18 15:16:55 ncq 2927 # - run dumb, brute person search query on really complex search terms 2928 # 2929 # Revision 1.57 2005/11/13 15:28:06 ncq 2930 # - properly fix unicode problem when normalizing name search terms 2931 # 2932 # Revision 1.56 2005/10/09 12:22:54 ihaywood 2933 # new rich text 2934 # widget 2935 # bugfix to gmperson.py 2936 # 2937 # Revision 1.55 2005/09/25 01:00:47 ihaywood 2938 # bugfixes 2939 # 2940 # remember 2.6 uses "import wx" not "from wxPython import wx" 2941 # removed not null constraint on clin_encounter.rfe as has no value on instantiation 2942 # client doesn't try to set clin_encounter.description as it doesn't exist anymore 2943 # 2944 # Revision 1.54 2005/09/19 16:33:31 ncq 2945 # - less incorrect message re EMR loading 2946 # 2947 # Revision 1.53 2005/09/12 15:06:20 ncq 2948 # - add space after title 2949 # 2950 # Revision 1.52 2005/09/11 17:25:31 ncq 2951 # - support force_reload in gmCurrentPatient - needed since Richard wants to 2952 # reload data when finding the same patient again 2953 # 2954 # Revision 1.51 2005/08/08 08:06:44 ncq 2955 # - cleanup 2956 # 2957 # Revision 1.50 2005/07/24 18:44:33 ncq 2958 # - actually, make it an outright error to stuff strings 2959 # into DateTime objects - as we can't know the format 2960 # we couldn't do much about it anyways ... callers better 2961 # do their part 2962 # 2963 # Revision 1.49 2005/07/24 18:38:42 ncq 2964 # - look out for strings being stuffed into datetime objects 2965 # 2966 # Revision 1.48 2005/06/04 09:30:08 ncq 2967 # - just silly whitespace cleanup 2968 # 2969 # Revision 1.47 2005/06/03 15:24:27 cfmoro 2970 # Fix to make lin_comm work. FIXME added 2971 # 2972 # Revision 1.46 2005/05/28 11:46:28 cfmoro 2973 # Evict cache in identity linking/add methods 2974 # 2975 # Revision 1.45 2005/05/23 12:01:07 cfmoro 2976 # Create/update comms 2977 # 2978 # Revision 1.44 2005/05/19 17:33:07 cfmoro 2979 # Minor fix 2980 # 2981 # Revision 1.43 2005/05/19 16:31:45 ncq 2982 # - handle state_code/country_code in identity.addresses subtable select 2983 # 2984 # Revision 1.42 2005/05/19 15:55:51 ncq 2985 # - de-escalated error level from Panic to Error on failing to add name/nickname 2986 # 2987 # Revision 1.41 2005/05/19 15:19:48 cfmoro 2988 # Minor fixes when object is None 2989 # 2990 # Revision 1.40 2005/05/18 08:27:14 cfmoro 2991 # link_communication failing becouse of situacion of add_to_subtable ( ? 2992 # 2993 # Revision 1.39 2005/05/17 18:01:19 ncq 2994 # - cleanup 2995 # 2996 # Revision 1.38 2005/05/17 14:41:36 cfmoro 2997 # Notebooked patient editor initial code 2998 # 2999 # Revision 1.37 2005/05/17 08:03:05 ncq 3000 # - fix unicode errors in DE query generator normalizer 3001 # 3002 # Revision 1.36 2005/05/14 15:06:18 ncq 3003 # - fix logging error 3004 # 3005 # Revision 1.35 2005/05/12 15:07:25 ncq 3006 # - add get_emr() 3007 # 3008 # Revision 1.34 2005/05/04 08:55:08 ncq 3009 # - streamlining 3010 # - comply with slightly changed subtables API 3011 # 3012 # Revision 1.33 2005/05/01 10:15:59 cfmoro 3013 # Link_XXX methods ported to take advantage of subtables framework. save_payload seems need fixing, as no values are dumped to backed 3014 # 3015 # Revision 1.32 2005/04/28 19:21:18 cfmoro 3016 # zip code streamlining 3017 # 3018 # Revision 1.31 2005/04/28 16:32:19 cfmoro 3019 # Leave town postcode out of linking an address 3020 # 3021 # Revision 1.30 2005/04/26 18:16:13 ncq 3022 # - cIdentity needs a cleanup() 3023 # 3024 # Revision 1.29 2005/04/23 08:48:52 cfmoro 3025 # Improved version of linking communications, controlling duplicates and medium in plpgsql 3026 # 3027 # Revision 1.28 2005/04/23 07:52:38 cfmoro 3028 # Added get_comm_list and cIdentity.link_communication methods 3029 # 3030 # Revision 1.27 2005/04/23 06:14:25 cfmoro 3031 # Added cIdentity.link_address method 3032 # 3033 # Revision 1.26 2005/04/20 21:55:39 ncq 3034 # - just some cleanup 3035 # 3036 # Revision 1.25 2005/04/19 19:51:49 cfmoro 3037 # Names cached in get_all_names. Added get_active_name 3038 # 3039 # Revision 1.24 2005/04/18 19:18:44 ncq 3040 # - cleanup, link_occuption doesn't work right yet 3041 # 3042 # Revision 1.23 2005/04/18 16:07:11 cfmoro 3043 # Improved sanity check in add_name 3044 # 3045 # Revision 1.22 2005/04/18 15:55:37 cfmoro 3046 # added set_nickname method, test code and minor update string fixes 3047 # 3048 # Revision 1.21 2005/04/14 22:34:50 ncq 3049 # - some streamlining of create_identity 3050 # 3051 # Revision 1.20 2005/04/14 19:27:20 cfmoro 3052 # Added title param to create_identity, to cover al fields in basic patient details 3053 # 3054 # Revision 1.19 2005/04/14 19:04:01 cfmoro 3055 # create_occupation -> add_occupation 3056 # 3057 # Revision 1.18 2005/04/14 18:58:14 cfmoro 3058 # Added create occupation method and minor gender map clean up, to replace later by get_gender_list 3059 # 3060 # Revision 1.17 2005/04/14 18:23:59 ncq 3061 # - get_gender_list() 3062 # 3063 # Revision 1.16 2005/04/14 08:51:13 ncq 3064 # - add cIdentity/dob2medical_age() from gmDemographicRecord.py 3065 # - make cIdentity inherit from cBusinessDBObject 3066 # - add create_identity() 3067 # 3068 # Revision 1.15 2005/03/20 16:49:07 ncq 3069 # - fix SQL syntax and do run all queries until identities found 3070 # - we now find Richard 3071 # - cleanup 3072 # 3073 # Revision 1.14 2005/03/18 07:44:10 ncq 3074 # - queries fixed but logic needs more work ! 3075 # 3076 # Revision 1.13 2005/03/16 12:57:26 sjtan 3077 # 3078 # fix import error. 3079 # 3080 # Revision 1.12 2005/03/08 16:43:58 ncq 3081 # - allow a cIdentity instance to be passed to gmCurrentPatient 3082 # 3083 # Revision 1.11 2005/02/19 15:06:33 sjtan 3084 # 3085 # **kwargs should be passed for signal parameters. 3086 # 3087 # Revision 1.10 2005/02/15 18:29:03 ncq 3088 # - test_result.id -> pk 3089 # 3090 # Revision 1.9 2005/02/13 15:23:31 ncq 3091 # - v_basic_person.i_pk -> pk_identity 3092 # 3093 # Revision 1.8 2005/02/12 13:50:25 ncq 3094 # - identity.id -> identity.pk and followup changes in v_basic_person 3095 # 3096 # Revision 1.7 2005/02/02 23:03:17 ncq 3097 # - change "demographic record" to "identity" 3098 # - dependant files still need being changed 3099 # 3100 # Revision 1.6 2005/02/01 19:27:56 ncq 3101 # - more renaming, I think we are getting there, if you think about it it 3102 # seems "demographic record" really is "identity" 3103 # 3104 # Revision 1.5 2005/02/01 19:14:10 ncq 3105 # - cleanup, internal renaming for consistency 3106 # - reallow cPerson to be instantiated with PK but retain main instantiation mode with cIdentity 3107 # - smarten up gmCurrentPatient() and re-add previous speedups 3108 # - do use ask_for_patient() in unit test 3109 # 3110 # Revision 1.4 2005/02/01 10:16:07 ihaywood 3111 # refactoring of gmDemographicRecord and follow-on changes as discussed. 3112 # 3113 # gmTopPanel moves to gmHorstSpace 3114 # gmRichardSpace added -- example code at present, haven't even run it myself 3115 # (waiting on some icon .pngs from Richard) 3116 # 3117 # Revision 1.3 2005/01/31 18:48:45 ncq 3118 # - self._patient -> self._person 3119 # - speedup 3120 # 3121 # Revision 1.2 2005/01/31 12:59:56 ncq 3122 # - cleanup, improved comments 3123 # - rename class gmPerson to cPerson 3124 # - add helpers prompted_input() and ask_for_patient() 3125 # 3126 # Revision 1.1 2005/01/31 10:24:17 ncq 3127 # - renamed from gmPatient.py 3128 # 3129 # Revision 1.56 2004/09/02 00:52:10 ncq 3130 # - wait, #digits may still be an external ID search so allow for that 3131 # 3132 # Revision 1.55 2004/09/02 00:37:49 ncq 3133 # - it's ~*, not *~ 3134 # 3135 # Revision 1.54 2004/09/01 21:57:55 ncq 3136 # - make search GnuMed primary key work 3137 # - add search for arbitrary external ID via "#..." 3138 # - fix regexing in __normalize() to avoid nested replacements 3139 # 3140 # Revision 1.53 2004/08/24 19:15:42 ncq 3141 # - __normalize_soundalikes() -> __normalize() + improve (apostrophy/hyphen) 3142 # 3143 # Revision 1.52 2004/08/24 14:27:06 ncq 3144 # - improve __normalize_soundalikes() 3145 # - fix nasty bug: missing ] resulting in endless logging 3146 # - prepare search on external id 3147 # 3148 # Revision 1.51 2004/08/20 13:28:16 ncq 3149 # - cleanup/improve inline docs 3150 # - allow gmCurrentPatient._patient to be reset to gmNull.cNull on aPKey = -1 3151 # - teach patient searcher about ", something" to be first name 3152 # 3153 # Revision 1.50 2004/08/18 08:13:51 ncq 3154 # - fixed encoding special comment 3155 # 3156 # Revision 1.49 2004/07/21 07:53:12 ncq 3157 # - some cleanup in set_active_patient 3158 # 3159 # Revision 1.48 2004/07/20 10:09:44 ncq 3160 # - a bit of cleanup here and there 3161 # - use Null design pattern instead of None when no real 3162 # patient connected to gmCurrentPatient Borg 3163 # 3164 # this allows us to forego all the tests for None as 3165 # Null() reliably does nothing no matter what you try, 3166 # eventually, this will allow us to remove all the 3167 # is_patient_avail checks in the frontend, 3168 # it also acts sanely for code forgetting to check 3169 # for a connected patient 3170 # 3171 # Revision 1.47 2004/07/20 01:01:44 ihaywood 3172 # changing a patients name works again. 3173 # Name searching has been changed to query on names rather than v_basic_person. 3174 # This is so the old (inactive) names are still visible to the search. 3175 # This is so when Mary Smith gets married, we can still find her under Smith. 3176 # [In Australia this odd tradition is still the norm, even female doctors 3177 # have their medical registration documents updated] 3178 # 3179 # SOAPTextCtrl now has popups, but the cursor vanishes (?) 3180 # 3181 # Revision 1.46 2004/07/15 23:30:11 ncq 3182 # - 'clinical_record' -> get_clinical_record() 3183 # 3184 # Revision 1.45 2004/07/05 22:26:24 ncq 3185 # - do some timings to find patient change time sinks 3186 # 3187 # Revision 1.44 2004/06/15 19:14:30 ncq 3188 # - add cleanup() to current patient calling gmPerson.cleanup() 3189 # 3190 # Revision 1.43 2004/06/01 23:58:01 ncq 3191 # - debugged dob handling in _make_queries_generic 3192 # 3193 # Revision 1.42 2004/06/01 07:50:56 ncq 3194 # - typo fix 3195 # 3196 # Revision 1.41 2004/05/18 22:38:19 ncq 3197 # - __patient -> _patient 3198 # 3199 # Revision 1.40 2004/05/18 20:40:11 ncq 3200 # - streamline __init__ significantly 3201 # - check return status of get_clinical_record() 3202 # - self.patient -> self.__patient 3203 # 3204 # Revision 1.39 2004/04/11 10:14:36 ncq 3205 # - fix b0rked dob/dod handling in query generation 3206 # - searching by dob should now work 3207 # 3208 # Revision 1.38 2004/03/25 11:14:48 ncq 3209 # - fix get_document_folder() 3210 # 3211 # Revision 1.37 2004/03/25 11:03:23 ncq 3212 # - getActiveName -> get_names 3213 # 3214 # Revision 1.36 2004/03/25 09:47:56 ncq 3215 # - fix whitespace breakage 3216 # 3217 # Revision 1.35 2004/03/23 15:04:59 ncq 3218 # - merge Carlos' constraints into get_text_dump 3219 # - add gmPatient.export_data() 3220 # 3221 # Revision 1.34 2004/03/20 19:44:50 ncq 3222 # - do import gmI18N 3223 # - only fetch i_id in queries 3224 # - revert to returning flat list of ids from get_patient_ids, must have been Syan fallout, I assume 3225 # 3226 # Revision 1.33 2004/03/20 13:31:18 ncq 3227 # - PostgreSQL has date_trunc, not datetrunc 3228 # 3229 # Revision 1.32 2004/03/20 13:14:36 ncq 3230 # - sync data dict and named substs in __generate_queries_generic 3231 # 3232 # Revision 1.31 2004/03/20 13:05:20 ncq 3233 # - we of course need to return results from __generate_queries_generic 3234 # 3235 # Revision 1.30 2004/03/20 12:49:55 ncq 3236 # - support gender, too, in search_dict in get_patient_ids 3237 # 3238 # Revision 1.29 2004/03/20 12:32:51 ncq 3239 # - check for query_lists is None in get_pat_ids 3240 # 3241 # Revision 1.28 2004/03/20 11:45:41 ncq 3242 # - don't pass search_dict[id] to get_patient_ids() 3243 # 3244 # Revision 1.27 2004/03/20 11:10:46 ncq 3245 # - where_snippets needs to be [] 3246 # 3247 # Revision 1.26 2004/03/20 10:48:31 ncq 3248 # - if search_dict given we need to pass it to run_ro_query 3249 # 3250 # Revision 1.25 2004/03/19 11:46:24 ncq 3251 # - add search_term to get_pat_ids() 3252 # 3253 # Revision 1.24 2004/03/10 13:44:33 ncq 3254 # - shouldn't just import gmI18N, needs fix, I guess 3255 # 3256 # Revision 1.23 2004/03/10 12:56:01 ihaywood 3257 # fixed sudden loss of main.shadow 3258 # more work on referrals, 3259 # 3260 # Revision 1.22 2004/03/10 00:09:51 ncq 3261 # - cleanup imports 3262 # 3263 # Revision 1.21 2004/03/09 07:34:51 ihaywood 3264 # reactivating plugins 3265 # 3266 # Revision 1.20 2004/03/07 23:52:32 ncq 3267 # - get_document_folder() 3268 # 3269 # Revision 1.19 2004/03/04 19:46:53 ncq 3270 # - switch to package based import: from Gnumed.foo import bar 3271 # 3272 # Revision 1.18 2004/02/25 09:46:20 ncq 3273 # - import from pycommon now, not python-common 3274 # 3275 # Revision 1.17 2004/02/18 06:36:04 ihaywood 3276 # bugfixes 3277 # 3278 # Revision 1.16 2004/02/17 10:38:27 ncq 3279 # - create_new_patient() -> xlnk_patient_in_clinical() 3280 # 3281 # Revision 1.15 2004/02/14 00:37:10 ihaywood 3282 # Bugfixes 3283 # - weeks = days / 7 3284 # - create_new_patient to maintain xlnk_identity in historica 3285 # 3286 # Revision 1.14 2004/02/05 18:38:56 ncq 3287 # - add .get_ID(), .is_locked() 3288 # - set_active_patient() convenience function 3289 # 3290 # Revision 1.13 2004/02/04 00:57:24 ncq 3291 # - added UI-independant patient search logic taken from gmPatientSelector 3292 # - we can now have a console patient search field just as powerful as 3293 # the GUI version due to it running the same business logic code 3294 # - also fixed _make_simple_query() results 3295 # 3296 # Revision 1.12 2004/01/18 21:43:00 ncq 3297 # - speed up get_clinical_record() 3298 # 3299 # Revision 1.11 2004/01/12 16:21:03 ncq 3300 # - _get_clini* -> get_clini* 3301 # 3302 # Revision 1.10 2003/11/20 01:17:14 ncq 3303 # - consensus was that N/A is no good for identity.gender hence 3304 # don't use it in create_dummy_identity anymore 3305 # 3306 # Revision 1.9 2003/11/18 16:35:17 ncq 3307 # - correct create_dummy_identity() 3308 # - move create_dummy_relative to gmDemographicRecord and rename it to link_new_relative 3309 # - remove reload keyword from gmCurrentPatient.__init__() - if you need it your logic 3310 # is screwed 3311 # 3312 # Revision 1.8 2003/11/17 10:56:34 sjtan 3313 # 3314 # synced and commiting. 3315 # 3316 # Revision 1.7 2003/11/16 10:58:36 shilbert 3317 # - corrected typo 3318 # 3319 # Revision 1.6 2003/11/09 16:39:34 ncq 3320 # - get handler now 'demographic record', not 'demographics' 3321 # 3322 # Revision 1.5 2003/11/04 00:07:40 ncq 3323 # - renamed gmDemographics 3324 # 3325 # Revision 1.4 2003/10/26 17:35:04 ncq 3326 # - conceptual cleanup 3327 # - IMHO, patient searching and database stub creation is OUTSIDE 3328 # THE SCOPE OF gmPerson and gmDemographicRecord 3329 # 3330 # Revision 1.3 2003/10/26 11:27:10 ihaywood 3331 # gmPatient is now the "patient stub", all demographics stuff in gmDemographics. 3332 # 3333 # Ergregious breakages are fixed, but needs more work 3334 # 3335 # Revision 1.2 2003/10/26 01:38:06 ncq 3336 # - gmTmpPatient -> gmPatient, cleanup 3337 # 3338 # Revision 1.1 2003/10/26 01:26:45 ncq 3339 # - now go live, this is for real 3340 # 3341 # Revision 1.41 2003/10/19 10:42:57 ihaywood 3342 # extra functions 3343 # 3344 # Revision 1.40 2003/09/24 08:45:40 ihaywood 3345 # NewAddress now functional 3346 # 3347 # Revision 1.39 2003/09/23 19:38:03 ncq 3348 # - cleanup 3349 # - moved GetAddressesType out of patient class - it's a generic function 3350 # 3351 # Revision 1.38 2003/09/23 12:49:56 ncq 3352 # - reformat, %d -> %s 3353 # 3354 # Revision 1.37 2003/09/23 12:09:26 ihaywood 3355 # Karsten, we've been tripping over each other again 3356 # 3357 # Revision 1.36 2003/09/23 11:31:12 ncq 3358 # - properly use ro_run_query()s returns 3359 # 3360 # Revision 1.35 2003/09/22 23:29:30 ncq 3361 # - new style run_ro_query() 3362 # 3363 # Revision 1.34 2003/09/21 12:46:30 ncq 3364 # - switched most ro queries to run_ro_query() 3365 # 3366 # Revision 1.33 2003/09/21 10:37:20 ncq 3367 # - bugfix, cleanup 3368 # 3369 # Revision 1.32 2003/09/21 06:53:40 ihaywood 3370 # bugfixes 3371 # 3372 # Revision 1.31 2003/09/17 11:08:30 ncq 3373 # - cleanup, fix type "personaliaa" 3374 # 3375 # Revision 1.30 2003/09/17 03:00:59 ihaywood 3376 # support for local inet connections 3377 # 3378 # Revision 1.29 2003/07/19 20:18:28 ncq 3379 # - code cleanup 3380 # - explicitely cleanup EMR when cleaning up patient 3381 # 3382 # Revision 1.28 2003/07/09 16:21:22 ncq 3383 # - better comments 3384 # 3385 # Revision 1.27 2003/06/27 16:04:40 ncq 3386 # - no ; in DB-API 3387 # 3388 # Revision 1.26 2003/06/26 21:28:02 ncq 3389 # - fatal->verbose, %s; quoting bug 3390 # 3391 # Revision 1.25 2003/06/22 16:18:34 ncq 3392 # - cleanup, send signal prior to changing the active patient, too 3393 # 3394 # Revision 1.24 2003/06/19 15:24:23 ncq 3395 # - add is_connected check to gmCurrentPatient to find 3396 # out whether there's a live patient record attached 3397 # - typo fix 3398 # 3399 # Revision 1.23 2003/06/01 14:34:47 sjtan 3400 # 3401 # hopefully complies with temporary model; not using setData now ( but that did work). 3402 # Please leave a working and tested substitute (i.e. select a patient , allergy list 3403 # will change; check allergy panel allows update of allergy list), if still 3404 # not satisfied. I need a working model-view connection ; trying to get at least 3405 # a basically database updating version going . 3406 # 3407 # Revision 1.22 2003/06/01 13:34:38 ncq 3408 # - reinstate remote app locking 3409 # - comment out thread lock for now but keep code 3410 # - setting gmCurrentPatient is not how it is supposed to work (I think) 3411 # 3412 # Revision 1.21 2003/06/01 13:20:32 sjtan 3413 # 3414 # logging to data stream for debugging. Adding DEBUG tags when work out how to use vi 3415 # with regular expression groups (maybe never). 3416 # 3417 # Revision 1.20 2003/06/01 01:47:32 sjtan 3418 # 3419 # starting allergy connections. 3420 # 3421 # Revision 1.19 2003/04/29 15:24:05 ncq 3422 # - add _get_clinical_record handler 3423 # - add _get_API API discovery handler 3424 # 3425 # Revision 1.18 2003/04/28 21:36:33 ncq 3426 # - compactify medical age 3427 # 3428 # Revision 1.17 2003/04/25 12:58:58 ncq 3429 # - dynamically handle supplied data in create_patient but added some sanity checks 3430 # 3431 # Revision 1.16 2003/04/19 22:54:46 ncq 3432 # - cleanup 3433 # 3434 # Revision 1.15 2003/04/19 14:59:04 ncq 3435 # - attribute handler for "medical age" 3436 # 3437 # Revision 1.14 2003/04/09 16:15:44 ncq 3438 # - get handler for age 3439 # 3440 # Revision 1.13 2003/04/04 20:40:51 ncq 3441 # - handle connection errors gracefully 3442 # - let gmCurrentPatient be a borg but let the person object be an attribute thereof 3443 # instead of an ancestor, this way we can safely do __init__(aPKey) where aPKey may or 3444 # may not be None 3445 # 3446 # Revision 1.12 2003/03/31 23:36:51 ncq 3447 # - adapt to changed view v_basic_person 3448 # 3449 # Revision 1.11 2003/03/27 21:08:25 ncq 3450 # - catch connection errors 3451 # - create_patient rewritten 3452 # - cleanup on __del__ 3453 # 3454 # Revision 1.10 2003/03/25 12:32:31 ncq 3455 # - create_patient helper 3456 # - __getTitle 3457 # 3458 # Revision 1.9 2003/02/21 16:42:02 ncq 3459 # - better error handling on query generation 3460 # 3461 # Revision 1.8 2003/02/18 02:41:54 ncq 3462 # - helper function get_patient_ids, only structured search term search implemented so far 3463 # 3464 # Revision 1.7 2003/02/17 16:16:13 ncq 3465 # - document list -> document id list 3466 # 3467 # Revision 1.6 2003/02/11 18:21:36 ncq 3468 # - move over to __getitem__ invoking handlers 3469 # - self.format to be used as an arbitrary format string 3470 # 3471 # Revision 1.5 2003/02/11 13:03:44 ncq 3472 # - don't change patient on patient not found ... 3473 # 3474 # Revision 1.4 2003/02/09 23:38:21 ncq 3475 # - now actually listens patient selectors, commits old patient and 3476 # inits the new one if possible 3477 # 3478 # Revision 1.3 2003/02/08 00:09:46 ncq 3479 # - finally starts being useful 3480 # 3481 # Revision 1.2 2003/02/06 15:40:58 ncq 3482 # - hit hard the merge wall 3483 # 3484 # Revision 1.1 2003/02/01 17:53:12 ncq 3485 # - doesn't do anything, just to show people where I am going 3486 # 3487