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 996 return gmDateTime.format_interval_medically ( 997 pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone) - dob 998 ) 999 1000 return u'%s%s' % ( 1001 gmTools.u_latin_cross, 1002 gmDateTime.format_interval_medically(self['deceased'] - dob) 1003 )
1004 #----------------------------------------------------------------------
1005 - def dob_in_range(self, min_distance=u'1 week', max_distance=u'1 week'):
1006 cmd = u'select dem.dob_is_in_range(%(dob)s, %(min)s, %(max)s)' 1007 rows, idx = gmPG2.run_ro_queries ( 1008 queries = [{ 1009 'cmd': cmd, 1010 'args': {'dob': self['dob'], 'min': min_distance, 'max': max_distance} 1011 }] 1012 ) 1013 return rows[0][0]
1014 #---------------------------------------------------------------------- 1015 # practice related 1016 #----------------------------------------------------------------------
1017 - def get_last_encounter(self):
1018 cmd = u'select * from clin.v_most_recent_encounters where pk_patient=%s' 1019 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self._payload[self._idx['pk_identity']]]}]) 1020 if len(rows) > 0: 1021 return rows[0] 1022 else: 1023 return None
1024 #--------------------------------------------------------
1025 - def _get_messages(self):
1026 return gmProviderInbox.get_inbox_messages(pk_patient = self._payload[self._idx['pk_identity']])
1027
1028 - def _set_messages(self, messages):
1029 return
1030 1031 messages = property(_get_messages, _set_messages) 1032 #--------------------------------------------------------
1033 - def delete_message(self, pk=None):
1035 #---------------------------------------------------------------------- 1036 # convenience 1037 #----------------------------------------------------------------------
1038 - def get_dirname(self):
1039 """Format patient demographics into patient specific path name fragment.""" 1040 return '%s-%s%s-%s' % ( 1041 self._payload[self._idx['lastnames']].replace(u' ', u'_'), 1042 self._payload[self._idx['firstnames']].replace(u' ', u'_'), 1043 gmTools.coalesce(self._payload[self._idx['preferred']], u'', template_initial = u'-(%s)'), 1044 self.get_formatted_dob(format = '%Y-%m-%d', encoding = gmI18N.get_encoding()) 1045 )
1046 #============================================================
1047 -class cStaffMember(cIdentity):
1048 """Represents a staff member which is a person. 1049 1050 - a specializing subclass of cIdentity turning it into a staff member 1051 """
1052 - def __init__(self, identity = None):
1053 cIdentity.__init__(self, identity=identity) 1054 self.__db_cache = {}
1055 #--------------------------------------------------------
1056 - def get_inbox(self):
1057 return gmProviderInbox.cProviderInbox(provider_id = self.ID)
1058 #============================================================
1059 -class cPatient(cIdentity):
1060 """Represents a person which is a patient. 1061 1062 - a specializing subclass of cIdentity turning it into a patient 1063 - its use is to cache subobjects like EMR and document folder 1064 """
1065 - def __init__(self, aPK_obj=None, row=None):
1066 cIdentity.__init__(self, aPK_obj=aPK_obj, row=row) 1067 self.__db_cache = {} 1068 self.__emr_access_lock = threading.Lock()
1069 #--------------------------------------------------------
1070 - def cleanup(self):
1071 """Do cleanups before dying. 1072 1073 - note that this may be called in a thread 1074 """ 1075 if self.__db_cache.has_key('clinical record'): 1076 self.__db_cache['clinical record'].cleanup() 1077 if self.__db_cache.has_key('document folder'): 1078 self.__db_cache['document folder'].cleanup() 1079 cIdentity.cleanup(self)
1080 #----------------------------------------------------------
1081 - def get_emr(self):
1082 if not self.__emr_access_lock.acquire(False): 1083 raise AttributeError('cannot access EMR') 1084 try: 1085 emr = self.__db_cache['clinical record'] 1086 self.__emr_access_lock.release() 1087 return emr 1088 except KeyError: 1089 pass 1090 1091 self.__db_cache['clinical record'] = gmClinicalRecord.cClinicalRecord(aPKey = self._payload[self._idx['pk_identity']]) 1092 self.__emr_access_lock.release() 1093 return self.__db_cache['clinical record']
1094 #--------------------------------------------------------
1095 - def get_document_folder(self):
1096 try: 1097 return self.__db_cache['document folder'] 1098 except KeyError: 1099 pass 1100 1101 self.__db_cache['document folder'] = gmDocuments.cDocumentFolder(aPKey = self._payload[self._idx['pk_identity']]) 1102 return self.__db_cache['document folder']
1103 #============================================================
1104 -class gmCurrentPatient(gmBorg.cBorg):
1105 """Patient Borg to hold currently active patient. 1106 1107 There may be many instances of this but they all share state. 1108 """
1109 - def __init__(self, patient=None, forced_reload=False):
1110 """Change or get currently active patient. 1111 1112 patient: 1113 * None: get currently active patient 1114 * -1: unset currently active patient 1115 * cPatient instance: set active patient if possible 1116 """ 1117 # make sure we do have a patient pointer 1118 try: 1119 tmp = self.patient 1120 except AttributeError: 1121 self.patient = gmNull.cNull() 1122 self.__register_interests() 1123 # set initial lock state, 1124 # this lock protects against activating another patient 1125 # when we are controlled from a remote application 1126 self.__lock_depth = 0 1127 # initialize callback state 1128 self.__pre_selection_callbacks = [] 1129 1130 # user wants copy of current patient 1131 if patient is None: 1132 return None 1133 1134 # do nothing if patient is locked 1135 if self.locked: 1136 _log.error('patient [%s] is locked, cannot change to [%s]' % (self.patient['pk_identity'], patient)) 1137 return None 1138 1139 # user wants to explicitly unset current patient 1140 if patient == -1: 1141 _log.debug('explicitly unsetting current patient') 1142 if not self.__run_pre_selection_callbacks(): 1143 _log.debug('not unsetting current patient') 1144 return None 1145 self.__send_pre_selection_notification() 1146 self.patient.cleanup() 1147 self.patient = gmNull.cNull() 1148 self.__send_selection_notification() 1149 return None 1150 1151 # must be cPatient instance, then 1152 if not isinstance(patient, cPatient): 1153 _log.error('cannot set active patient to [%s], must be either None, -1 or cPatient instance' % str(patient)) 1154 raise TypeError, 'gmPerson.gmCurrentPatient.__init__(): <patient> must be None, -1 or cPatient instance but is: %s' % str(patient) 1155 1156 # same ID, no change needed 1157 if (self.patient['pk_identity'] == patient['pk_identity']) and not forced_reload: 1158 return None 1159 1160 # user wants different patient 1161 _log.debug('patient change [%s] -> [%s] requested', self.patient['pk_identity'], patient['pk_identity']) 1162 1163 # everything seems swell 1164 if not self.__run_pre_selection_callbacks(): 1165 _log.debug('not changing current patient') 1166 return None 1167 self.__send_pre_selection_notification() 1168 self.patient.cleanup() 1169 self.patient = patient 1170 self.patient.get_emr() 1171 self.__send_selection_notification() 1172 1173 return None
1174 #--------------------------------------------------------
1175 - def __register_interests(self):
1176 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_identity_change) 1177 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_identity_change)
1178 #--------------------------------------------------------
1179 - def _on_identity_change(self):
1180 """Listen for patient *data* change.""" 1181 self.patient.refetch_payload()
1182 #-------------------------------------------------------- 1183 # external API 1184 #--------------------------------------------------------
1185 - def register_pre_selection_callback(self, callback=None):
1186 if not callable(callback): 1187 raise TypeError(u'callback [%s] not callable' % callback) 1188 1189 self.__pre_selection_callbacks.append(callback)
1190 #--------------------------------------------------------
1191 - def _get_connected(self):
1192 return (not isinstance(self.patient, gmNull.cNull))
1193
1194 - def _set_connected(self):
1195 raise AttributeError(u'invalid to set <connected> state')
1196 1197 connected = property(_get_connected, _set_connected) 1198 #--------------------------------------------------------
1199 - def _get_locked(self):
1200 return (self.__lock_depth > 0)
1201
1202 - def _set_locked(self, locked):
1203 if locked: 1204 self.__lock_depth = self.__lock_depth + 1 1205 gmDispatcher.send(signal='patient_locked') 1206 else: 1207 if self.__lock_depth == 0: 1208 _log.error('lock/unlock imbalance, trying to refcount lock depth below 0') 1209 return 1210 else: 1211 self.__lock_depth = self.__lock_depth - 1 1212 gmDispatcher.send(signal='patient_unlocked')
1213 1214 locked = property(_get_locked, _set_locked) 1215 #--------------------------------------------------------
1216 - def force_unlock(self):
1217 _log.info('forced patient unlock at lock depth [%s]' % self.__lock_depth) 1218 self.__lock_depth = 0 1219 gmDispatcher.send(signal='patient_unlocked')
1220 #-------------------------------------------------------- 1221 # patient change handling 1222 #--------------------------------------------------------
1224 if isinstance(self.patient, gmNull.cNull): 1225 return True 1226 1227 for call_back in self.__pre_selection_callbacks: 1228 try: 1229 successful = call_back() 1230 except: 1231 _log.exception('callback [%s] failed', call_back) 1232 print "*** pre-selection callback failed ***" 1233 print type(call_back) 1234 print call_back 1235 return False 1236 1237 if not successful: 1238 _log.debug('callback [%s] returned False', call_back) 1239 return False 1240 1241 return True
1242 #--------------------------------------------------------
1244 """Sends signal when another patient is about to become active. 1245 1246 This does NOT wait for signal handlers to complete. 1247 """ 1248 kwargs = { 1249 'signal': u'pre_patient_selection', 1250 'sender': id(self.__class__), 1251 'pk_identity': self.patient['pk_identity'] 1252 } 1253 gmDispatcher.send(**kwargs)
1254 #--------------------------------------------------------
1256 """Sends signal when another patient has actually been made active.""" 1257 kwargs = { 1258 'signal': u'post_patient_selection', 1259 'sender': id(self.__class__), 1260 'pk_identity': self.patient['pk_identity'] 1261 } 1262 gmDispatcher.send(**kwargs)
1263 #-------------------------------------------------------- 1264 # __getattr__ handling 1265 #--------------------------------------------------------
1266 - def __getattr__(self, attribute):
1267 if attribute == 'patient': 1268 raise AttributeError 1269 if not isinstance(self.patient, gmNull.cNull): 1270 return getattr(self.patient, attribute)
1271 #-------------------------------------------------------- 1272 # __get/setitem__ handling 1273 #--------------------------------------------------------
1274 - def __getitem__(self, attribute = None):
1275 """Return any attribute if known how to retrieve it by proxy. 1276 """ 1277 return self.patient[attribute]
1278 #--------------------------------------------------------
1279 - def __setitem__(self, attribute, value):
1280 self.patient[attribute] = value
1281 #============================================================
1282 -class cPatientSearcher_SQL:
1283 """UI independant i18n aware patient searcher."""
1284 - def __init__(self):
1285 self._generate_queries = self._generate_queries_de 1286 # make a cursor 1287 self.conn = gmPG2.get_connection() 1288 self.curs = self.conn.cursor()
1289 #--------------------------------------------------------
1290 - def __del__(self):
1291 try: 1292 self.curs.close() 1293 except: pass 1294 try: 1295 self.conn.close() 1296 except: pass
1297 #-------------------------------------------------------- 1298 # public API 1299 #--------------------------------------------------------
1300 - def get_patients(self, search_term = None, a_locale = None, dto = None):
1301 identities = self.get_identities(search_term, a_locale, dto) 1302 if identities is None: 1303 return None 1304 return [cPatient(aPK_obj=ident['pk_identity']) for ident in identities]
1305 #--------------------------------------------------------
1306 - def get_identities(self, search_term = None, a_locale = None, dto = None):
1307 """Get patient identity objects for given parameters. 1308 1309 - either search term or search dict 1310 - dto contains structured data that doesn't need to be parsed (cDTO_person) 1311 - dto takes precedence over search_term 1312 """ 1313 parse_search_term = (dto is None) 1314 1315 if not parse_search_term: 1316 queries = self._generate_queries_from_dto(dto) 1317 if queries is None: 1318 parse_search_term = True 1319 if len(queries) == 0: 1320 parse_search_term = True 1321 1322 if parse_search_term: 1323 # temporary change of locale for selecting query generator 1324 if a_locale is not None: 1325 print "temporary change of locale on patient search not implemented" 1326 _log.warning("temporary change of locale on patient search not implemented") 1327 # generate queries 1328 if search_term is None: 1329 raise ValueError('need search term (dto AND search_term are None)') 1330 1331 queries = self._generate_queries(search_term) 1332 1333 # anything to do ? 1334 if len(queries) == 0: 1335 _log.error('query tree empty') 1336 _log.error('[%s] [%s] [%s]' % (search_term, a_locale, str(dto))) 1337 return None 1338 1339 # collect IDs here 1340 identities = [] 1341 # cycle through query list 1342 for query in queries: 1343 _log.debug("running %s" % query) 1344 try: 1345 rows, idx = gmPG2.run_ro_queries(queries = [query], get_col_idx=True) 1346 except: 1347 _log.exception('error running query') 1348 continue 1349 if len(rows) == 0: 1350 continue 1351 identities.extend ( 1352 [ cIdentity(row = {'pk_field': 'pk_identity', 'data': row, 'idx': idx}) for row in rows ] 1353 ) 1354 1355 pks = [] 1356 unique_identities = [] 1357 for identity in identities: 1358 if identity['pk_identity'] in pks: 1359 continue 1360 pks.append(identity['pk_identity']) 1361 unique_identities.append(identity) 1362 1363 return unique_identities
1364 #-------------------------------------------------------- 1365 # internal helpers 1366 #--------------------------------------------------------
1367 - def _normalize_soundalikes(self, aString = None, aggressive = False):
1368 """Transform some characters into a regex.""" 1369 if aString.strip() == u'': 1370 return aString 1371 1372 # umlauts 1373 normalized = aString.replace(u'Ä', u'(Ä|AE|Ae|A|E)') 1374 normalized = normalized.replace(u'Ö', u'(Ö|OE|Oe|O)') 1375 normalized = normalized.replace(u'Ü', u'(Ü|UE|Ue|U)') 1376 normalized = normalized.replace(u'ä', u'(ä|ae|e|a)') 1377 normalized = normalized.replace(u'ö', u'(ö|oe|o)') 1378 normalized = normalized.replace(u'ü', u'(ü|ue|u|y)') 1379 normalized = normalized.replace(u'ß', u'(ß|sz|ss|s)') 1380 1381 # common soundalikes 1382 # - René, Desiré, Inés ... 1383 normalized = normalized.replace(u'é', u'***DUMMY***') 1384 normalized = normalized.replace(u'è', u'***DUMMY***') 1385 normalized = normalized.replace(u'***DUMMY***', u'(é|e|è|ä|ae)') 1386 1387 # FIXME: missing i/a/o - but uncommon in German 1388 normalized = normalized.replace(u'v', u'***DUMMY***') 1389 normalized = normalized.replace(u'f', u'***DUMMY***') 1390 normalized = normalized.replace(u'ph', u'***DUMMY***') # now, this is *really* specific for German 1391 normalized = normalized.replace(u'***DUMMY***', u'(v|f|ph)') 1392 1393 # silent characters (Thomas vs Tomas) 1394 normalized = normalized.replace(u'Th',u'***DUMMY***') 1395 normalized = normalized.replace(u'T', u'***DUMMY***') 1396 normalized = normalized.replace(u'***DUMMY***', u'(Th|T)') 1397 normalized = normalized.replace(u'th', u'***DUMMY***') 1398 normalized = normalized.replace(u't', u'***DUMMY***') 1399 normalized = normalized.replace(u'***DUMMY***', u'(th|t)') 1400 1401 # apostrophes, hyphens et al 1402 normalized = normalized.replace(u'"', u'***DUMMY***') 1403 normalized = normalized.replace(u"'", u'***DUMMY***') 1404 normalized = normalized.replace(u'`', u'***DUMMY***') 1405 normalized = normalized.replace(u'***DUMMY***', u"""("|'|`|***DUMMY***|\s)*""") 1406 normalized = normalized.replace(u'-', u"""(-|\s)*""") 1407 normalized = normalized.replace(u'|***DUMMY***|', u'|-|') 1408 1409 if aggressive: 1410 pass 1411 # some more here 1412 1413 _log.debug('[%s] -> [%s]' % (aString, normalized)) 1414 1415 return normalized
1416 #-------------------------------------------------------- 1417 # write your own query generator and add it here: 1418 # use compile() for speedup 1419 # must escape strings before use !! 1420 # ORDER BY ! 1421 # FIXME: what about "< 40" ? 1422 #--------------------------------------------------------
1423 - def _generate_simple_query(self, raw):
1424 """Compose queries if search term seems unambigous.""" 1425 queries = [] 1426 1427 # "<digits>" - GNUmed patient PK or DOB 1428 if regex.match(u"^(\s|\t)*\d+(\s|\t)*$", raw, flags = regex.LOCALE | regex.UNICODE): 1429 _log.debug("[%s]: a PK or DOB" % raw) 1430 tmp = raw.strip() 1431 queries.append ({ 1432 'cmd': u"select *, %s::text as match_type FROM dem.v_basic_person WHERE pk_identity = %s order by lastnames, firstnames, dob", 1433 'args': [_('internal patient ID'), tmp] 1434 }) 1435 queries.append ({ 1436 '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", 1437 'args': [_('date of birth'), tmp.replace(',', '.')] 1438 }) 1439 queries.append ({ 1440 'cmd': u""" 1441 select vba.*, %s::text as match_type from dem.lnk_identity2ext_id li2ext_id, dem.v_basic_person vba 1442 where vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s) 1443 order by lastnames, firstnames, dob""", 1444 'args': [_('external patient ID'), tmp] 1445 }) 1446 return queries 1447 1448 # "<d igi ts>" - DOB or patient PK 1449 if regex.match(u"^(\d|\s|\t)+$", raw, flags = regex.LOCALE | regex.UNICODE): 1450 _log.debug("[%s]: a DOB or PK" % raw) 1451 queries.append ({ 1452 '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", 1453 'args': [_('date of birth'), raw.replace(',', '.')] 1454 }) 1455 tmp = raw.replace(u' ', u'') 1456 tmp = tmp.replace(u'\t', u'') 1457 queries.append ({ 1458 'cmd': u"SELECT *, %s::text as match_type from dem.v_basic_person WHERE pk_identity LIKE %s%%", 1459 'args': [_('internal patient ID'), tmp] 1460 }) 1461 return queries 1462 1463 # "#<di git s>" - GNUmed patient PK 1464 if regex.match(u"^(\s|\t)*#(\d|\s|\t)+$", raw, flags = regex.LOCALE | regex.UNICODE): 1465 _log.debug("[%s]: a PK or external ID" % raw) 1466 tmp = raw.replace(u'#', u'') 1467 tmp = tmp.strip() 1468 tmp = tmp.replace(u' ', u'') 1469 tmp = tmp.replace(u'\t', u'') 1470 # this seemingly stupid query ensures the PK actually exists 1471 queries.append ({ 1472 'cmd': u"SELECT *, %s::text as match_type from dem.v_basic_person WHERE pk_identity = %s order by lastnames, firstnames, dob", 1473 'args': [_('internal patient ID'), tmp] 1474 }) 1475 # but might also be an external ID 1476 tmp = raw.replace(u'#', u'') 1477 tmp = tmp.strip() 1478 tmp = tmp.replace(u' ', u'***DUMMY***') 1479 tmp = tmp.replace(u'\t', u'***DUMMY***') 1480 tmp = tmp.replace(u'***DUMMY***', u'(\s|\t|-|/)*') 1481 queries.append ({ 1482 'cmd': u""" 1483 select vba.*, %s::text as match_type from dem.lnk_identity2ext_id li2ext_id, dem.v_basic_person vba 1484 where vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s) 1485 order by lastnames, firstnames, dob""", 1486 'args': [_('external patient ID'), tmp] 1487 }) 1488 return queries 1489 1490 # "#<di/git s or c-hars>" - external ID (or PUPIC) 1491 if regex.match(u"^(\s|\t)*#.+$", raw, flags = regex.LOCALE | regex.UNICODE): 1492 _log.debug("[%s]: an external ID" % raw) 1493 tmp = raw.replace(u'#', u'') 1494 tmp = tmp.strip() 1495 tmp = tmp.replace(u' ', u'***DUMMY***') 1496 tmp = tmp.replace(u'\t', u'***DUMMY***') 1497 tmp = tmp.replace(u'-', u'***DUMMY***') 1498 tmp = tmp.replace(u'/', u'***DUMMY***') 1499 tmp = tmp.replace(u'***DUMMY***', u'(\s|\t|-|/)*') 1500 queries.append ({ 1501 'cmd': u""" 1502 select vba.*, %s::text as match_type from dem.lnk_identity2ext_id li2ext_id, dem.v_basic_person vba 1503 where vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s) 1504 order by lastnames, firstnames, dob""", 1505 'args': [_('external patient ID'), tmp] 1506 }) 1507 return queries 1508 1509 # digits interspersed with "./-" or blank space - DOB 1510 if regex.match(u"^(\s|\t)*\d+(\s|\t|\.|\-|/)*\d+(\s|\t|\.|\-|/)*\d+(\s|\t|\.)*$", raw, flags = regex.LOCALE | regex.UNICODE): 1511 _log.debug("[%s]: a DOB" % raw) 1512 tmp = raw.strip() 1513 while u'\t\t' in tmp: tmp = tmp.replace(u'\t\t', u' ') 1514 while u' ' in tmp: tmp = tmp.replace(u' ', u' ') 1515 # apparently not needed due to PostgreSQL smarts... 1516 #tmp = tmp.replace('-', '.') 1517 #tmp = tmp.replace('/', '.') 1518 queries.append ({ 1519 '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", 1520 'args': [_('date of birth'), tmp.replace(',', '.')] 1521 }) 1522 return queries 1523 1524 # " , <alpha>" - first name 1525 if regex.match(u"^(\s|\t)*,(\s|\t)*([^0-9])+(\s|\t)*$", raw, flags = regex.LOCALE | regex.UNICODE): 1526 _log.debug("[%s]: a firstname" % raw) 1527 tmp = self._normalize_soundalikes(raw[1:].strip()) 1528 cmd = u""" 1529 SELECT DISTINCT ON (pk_identity) * from ( 1530 select *, %s as match_type from (( 1531 select vbp.* 1532 FROM dem.names, dem.v_basic_person vbp 1533 WHERE dem.names.firstnames ~ %s and vbp.pk_identity = dem.names.id_identity 1534 ) union all ( 1535 select vbp.* 1536 FROM dem.names, dem.v_basic_person vbp 1537 WHERE dem.names.firstnames ~ %s and vbp.pk_identity = dem.names.id_identity 1538 )) as super_list order by lastnames, firstnames, dob 1539 ) as sorted_list""" 1540 queries.append ({ 1541 'cmd': cmd, 1542 'args': [_('first name'), '^' + gmTools.capitalize(tmp, mode=gmTools.CAPS_NAMES), '^' + tmp] 1543 }) 1544 return queries 1545 1546 # "*|$<...>" - DOB 1547 if regex.match(u"^(\s|\t)*(\*|\$).+$", raw, flags = regex.LOCALE | regex.UNICODE): 1548 _log.debug("[%s]: a DOB" % raw) 1549 tmp = raw.replace(u'*', u'') 1550 tmp = tmp.replace(u'$', u'') 1551 queries.append ({ 1552 '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", 1553 'args': [_('date of birth'), tmp.replace(u',', u'.')] 1554 }) 1555 return queries 1556 1557 return queries # = []
1558 #-------------------------------------------------------- 1559 # generic, locale independant queries 1560 #--------------------------------------------------------
1561 - def _generate_queries_from_dto(self, dto = None):
1562 """Generate generic queries. 1563 1564 - not locale dependant 1565 - data -> firstnames, lastnames, dob, gender 1566 """ 1567 _log.debug(u'_generate_queries_from_dto("%s")' % dto) 1568 1569 if not isinstance(dto, cDTO_person): 1570 return None 1571 1572 vals = [_('name, gender, date of birth')] 1573 where_snippets = [] 1574 1575 vals.append(dto.firstnames) 1576 where_snippets.append(u'firstnames=%s') 1577 vals.append(dto.lastnames) 1578 where_snippets.append(u'lastnames=%s') 1579 1580 if dto.dob is not None: 1581 vals.append(dto.dob) 1582 #where_snippets.append(u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)") 1583 where_snippets.append(u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s)") 1584 1585 if dto.gender is not None: 1586 vals.append(dto.gender) 1587 where_snippets.append('gender=%s') 1588 1589 # sufficient data ? 1590 if len(where_snippets) == 0: 1591 _log.error('invalid search dict structure') 1592 _log.debug(data) 1593 return None 1594 1595 cmd = u""" 1596 select *, %%s as match_type from dem.v_basic_person 1597 where pk_identity in ( 1598 select id_identity from dem.names where %s 1599 ) order by lastnames, firstnames, dob""" % ' and '.join(where_snippets) 1600 1601 queries = [ 1602 {'cmd': cmd, 'args': vals} 1603 ] 1604 1605 # shall we mogrify name parts ? probably not 1606 1607 return queries
1608 #-------------------------------------------------------- 1609 # queries for DE 1610 #--------------------------------------------------------
1611 - def _generate_queries_de(self, search_term = None):
1612 1613 if search_term is None: 1614 return [] 1615 1616 # check to see if we get away with a simple query ... 1617 queries = self._generate_simple_query(search_term) 1618 if len(queries) > 0: 1619 return queries 1620 1621 _log.debug('[%s]: not a search term with a "suggestive" structure' % search_term) 1622 1623 # no we don't 1624 queries = [] 1625 1626 # replace Umlauts 1627 normalized = self._normalize_soundalikes(search_term) 1628 1629 # "<CHARS>" - single name part 1630 # yes, I know, this is culture specific (did you read the docs ?) 1631 if regex.match(u"^(\s|\t)*[a-zäöüßéáúóçøA-ZÄÖÜÇØ]+(\s|\t)*$", search_term, flags = regex.LOCALE | regex.UNICODE): 1632 # there's no intermediate whitespace due to the regex 1633 cmd = u""" 1634 SELECT DISTINCT ON (pk_identity) * from ( 1635 select * from (( 1636 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) 1637 ) union all ( 1638 -- first name 1639 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) 1640 ) union all ( 1641 -- anywhere in name 1642 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) 1643 )) as super_list order by lastnames, firstnames, dob 1644 ) as sorted_list""" 1645 tmp = normalized.strip() 1646 args = [] 1647 args.append(_('last name')) 1648 args.append('^' + tmp) 1649 args.append(_('first name')) 1650 args.append('^' + tmp) 1651 args.append(_('any name part')) 1652 args.append(tmp) 1653 1654 queries.append ({ 1655 'cmd': cmd, 1656 'args': args 1657 }) 1658 return queries 1659 1660 # try to split on (major) part separators 1661 parts_list = regex.split(u",|;", normalized) 1662 1663 # only one "major" part ? (i.e. no ",;" ?) 1664 if len(parts_list) == 1: 1665 # re-split on whitespace 1666 sub_parts_list = regex.split(u"\s*|\t*", normalized) 1667 1668 # parse into name/date parts 1669 date_count = 0 1670 name_parts = [] 1671 for part in sub_parts_list: 1672 # any digit signifies a date 1673 # FIXME: what about "<40" ? 1674 if regex.search(u"\d", part, flags = regex.LOCALE | regex.UNICODE): 1675 date_count = date_count + 1 1676 date_part = part 1677 else: 1678 name_parts.append(part) 1679 1680 # exactly 2 words ? 1681 if len(sub_parts_list) == 2: 1682 # no date = "first last" or "last first" 1683 if date_count == 0: 1684 # assumption: first last 1685 queries.append ({ 1686 '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", 1687 'args': [_('name: first-last'), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES)] 1688 }) 1689 queries.append ({ 1690 '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)", 1691 'args': [_('name: first-last'), '^' + name_parts[0], '^' + name_parts[1]] 1692 }) 1693 # assumption: last first 1694 queries.append ({ 1695 '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", 1696 'args': [_('name: last-first'), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES)] 1697 }) 1698 queries.append ({ 1699 '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)", 1700 'args': [_('name: last-first'), '^' + name_parts[1], '^' + name_parts[0]] 1701 }) 1702 # name parts anywhere in name - third order query ... 1703 queries.append ({ 1704 '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)", 1705 'args': [_('name'), name_parts[0], name_parts[1]] 1706 }) 1707 return queries 1708 # FIXME: either "name date" or "date date" 1709 _log.error("don't know how to generate queries for [%s]" % search_term) 1710 return queries 1711 1712 # exactly 3 words ? 1713 if len(sub_parts_list) == 3: 1714 # special case: 3 words, exactly 1 of them a date, no ",;" 1715 if date_count == 1: 1716 # assumption: first, last, dob - first order 1717 queries.append ({ 1718 '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)", 1719 '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'.')] 1720 }) 1721 queries.append ({ 1722 '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)", 1723 'args': [_('names: first-last, date of birth'), '^' + name_parts[0], '^' + name_parts[1], date_part.replace(u',', u'.')] 1724 }) 1725 # assumption: last, first, dob - second order query 1726 queries.append ({ 1727 '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)", 1728 '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'.')] 1729 }) 1730 queries.append ({ 1731 '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)", 1732 'args': [_('names: last-first, dob'), '^' + name_parts[1], '^' + name_parts[0], date_part.replace(u',', u'.')] 1733 }) 1734 # name parts anywhere in name - third order query ... 1735 queries.append ({ 1736 '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)", 1737 'args': [_('name, date of birth'), name_parts[0], name_parts[1], date_part.replace(u',', u'.')] 1738 }) 1739 return queries 1740 # FIXME: "name name name" or "name date date" 1741 queries.append(self._generate_dumb_brute_query(search_term)) 1742 return queries 1743 1744 # FIXME: no ',;' but neither "name name" nor "name name date" 1745 queries.append(self._generate_dumb_brute_query(search_term)) 1746 return queries 1747 1748 # more than one major part (separated by ';,') 1749 else: 1750 # parse into name and date parts 1751 date_parts = [] 1752 name_parts = [] 1753 name_count = 0 1754 for part in parts_list: 1755 # any digits ? 1756 if regex.search(u"\d+", part, flags = regex.LOCALE | regex.UNICODE): 1757 # FIXME: parse out whitespace *not* adjacent to a *word* 1758 date_parts.append(part) 1759 else: 1760 tmp = part.strip() 1761 tmp = regex.split(u"\s*|\t*", tmp) 1762 name_count = name_count + len(tmp) 1763 name_parts.append(tmp) 1764 1765 where_parts = [] 1766 # first, handle name parts 1767 # special case: "<date(s)>, <name> <name>, <date(s)>" 1768 if (len(name_parts) == 1) and (name_count == 2): 1769 # usually "first last" 1770 where_parts.append ({ 1771 'conditions': u"firstnames ~ %s and lastnames ~ %s", 1772 'args': [_('names: first last'), '^' + gmTools.capitalize(name_parts[0][0], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0][1], mode=gmTools.CAPS_NAMES)] 1773 }) 1774 where_parts.append ({ 1775 'conditions': u"lower(firstnames) ~* lower(%s) and lower(lastnames) ~* lower(%s)", 1776 'args': [_('names: first last'), '^' + name_parts[0][0], '^' + name_parts[0][1]] 1777 }) 1778 # but sometimes "last first"" 1779 where_parts.append ({ 1780 'conditions': u"firstnames ~ %s and lastnames ~ %s", 1781 'args': [_('names: last, first'), '^' + gmTools.capitalize(name_parts[0][1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0][0], mode=gmTools.CAPS_NAMES)] 1782 }) 1783 where_parts.append ({ 1784 'conditions': u"lower(firstnames) ~* lower(%s) and lower(lastnames) ~* lower(%s)", 1785 'args': [_('names: last, first'), '^' + name_parts[0][1], '^' + name_parts[0][0]] 1786 }) 1787 # or even substrings anywhere in name 1788 where_parts.append ({ 1789 'conditions': u"lower(firstnames || lastnames) ~* lower(%s) OR lower(firstnames || lastnames) ~* lower(%s)", 1790 'args': [_('name'), name_parts[0][0], name_parts[0][1]] 1791 }) 1792 1793 # special case: "<date(s)>, <name(s)>, <name(s)>, <date(s)>" 1794 elif len(name_parts) == 2: 1795 # usually "last, first" 1796 where_parts.append ({ 1797 'conditions': u"firstnames ~ %s AND lastnames ~ %s", 1798 'args': [_('name: last, first'), '^' + ' '.join(map(gmTools.capitalize, name_parts[1])), '^' + ' '.join(map(gmTools.capitalize, name_parts[0]))] 1799 }) 1800 where_parts.append ({ 1801 'conditions': u"lower(firstnames) ~* lower(%s) AND lower(lastnames) ~* lower(%s)", 1802 'args': [_('name: last, first'), '^' + ' '.join(name_parts[1]), '^' + ' '.join(name_parts[0])] 1803 }) 1804 # but sometimes "first, last" 1805 where_parts.append ({ 1806 'conditions': u"firstnames ~ %s AND lastnames ~ %s", 1807 'args': [_('name: last, first'), '^' + ' '.join(map(gmTools.capitalize, name_parts[0])), '^' + ' '.join(map(gmTools.capitalize, name_parts[1]))] 1808 }) 1809 where_parts.append ({ 1810 'conditions': u"lower(firstnames) ~* lower(%s) AND lower(lastnames) ~* lower(%s)", 1811 'args': [_('name: last, first'), '^' + ' '.join(name_parts[0]), '^' + ' '.join(name_parts[1])] 1812 }) 1813 # or even substrings anywhere in name 1814 where_parts.append ({ 1815 'conditions': u"lower(firstnames || lastnames) ~* lower(%s) AND lower(firstnames || lastnames) ~* lower(%s)", 1816 'args': [_('name'), ' '.join(name_parts[0]), ' '.join(name_parts[1])] 1817 }) 1818 1819 # big trouble - arbitrary number of names 1820 else: 1821 # FIXME: deep magic, not sure of rationale ... 1822 if len(name_parts) == 1: 1823 for part in name_parts[0]: 1824 where_parts.append ({ 1825 'conditions': u"lower(firstnames || lastnames) ~* lower(%s)", 1826 'args': [_('name'), part] 1827 }) 1828 else: 1829 tmp = [] 1830 for part in name_parts: 1831 tmp.append(' '.join(part)) 1832 for part in tmp: 1833 where_parts.append ({ 1834 'conditions': u"lower(firstnames || lastnames) ~* lower(%s)", 1835 'args': [_('name'), part] 1836 }) 1837 1838 # secondly handle date parts 1839 # FIXME: this needs a considerable smart-up ! 1840 if len(date_parts) == 1: 1841 if len(where_parts) == 0: 1842 where_parts.append ({ 1843 'conditions': u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)", 1844 'args': [_('date of birth'), date_parts[0].replace(u',', u'.')] 1845 }) 1846 if len(where_parts) > 0: 1847 where_parts[0]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)" 1848 where_parts[0]['args'].append(date_parts[0].replace(u',', u'.')) 1849 where_parts[0]['args'][0] += u', ' + _('date of birth') 1850 if len(where_parts) > 1: 1851 where_parts[1]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)" 1852 where_parts[1]['args'].append(date_parts[0].replace(u',', u'.')) 1853 where_parts[1]['args'][0] += u', ' + _('date of birth') 1854 elif len(date_parts) > 1: 1855 if len(where_parts) == 0: 1856 where_parts.append ({ 1857 '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)", 1858 'args': [_('date of birth/death'), date_parts[0].replace(u',', u'.'), date_parts[1].replace(u',', u'.')] 1859 }) 1860 if len(where_parts) > 0: 1861 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)", 1862 where_parts[0]['args'].append(date_parts[0].replace(u',', u'.'), date_parts[1].replace(u',', u'.')) 1863 where_parts[0]['args'][0] += u', ' + _('date of birth/death') 1864 if len(where_parts) > 1: 1865 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)", 1866 where_parts[1]['args'].append(date_parts[0].replace(u',', u'.'), date_parts[1].replace(u',', u'.')) 1867 where_parts[1]['args'][0] += u', ' + _('date of birth/death') 1868 1869 # and finally generate the queries ... 1870 for where_part in where_parts: 1871 queries.append ({ 1872 'cmd': u"select *, %%s::text as match_type from dem.v_basic_person where %s" % where_part['conditions'], 1873 'args': where_part['args'] 1874 }) 1875 return queries 1876 1877 return []
1878 #--------------------------------------------------------
1879 - def _generate_dumb_brute_query(self, search_term=''):
1880 1881 _log.debug('_generate_dumb_brute_query("%s")' % search_term) 1882 1883 where_clause = '' 1884 args = [] 1885 # FIXME: split on more than just ' ' 1886 for arg in search_term.strip().split(): 1887 where_clause += u' and lower(vbp.title || vbp.firstnames || vbp.lastnames) ~* lower(%s)' 1888 args.append(arg) 1889 1890 query = u""" 1891 select distinct on (pk_identity) * from ( 1892 select 1893 vbp.*, '%s'::text as match_type 1894 from 1895 dem.v_basic_person vbp, 1896 dem.names n 1897 where 1898 vbp.pk_identity = n.id_identity 1899 %s 1900 order by 1901 lastnames, 1902 firstnames, 1903 dob 1904 ) as ordered_list""" % (_('full name'), where_clause) 1905 1906 return ({'cmd': query, 'args': args})
1907 #============================================================ 1908 # match providers 1909 #============================================================
1910 -class cMatchProvider_Provider(gmMatchProvider.cMatchProvider_SQL2):
1911 - def __init__(self):
1912 gmMatchProvider.cMatchProvider_SQL2.__init__( 1913 self, 1914 queries = [ 1915 u"""select 1916 pk_staff, 1917 short_alias || ' (' || coalesce(title, '') || firstnames || ' ' || lastnames || ')', 1918 1 1919 from dem.v_staff 1920 where 1921 is_active and ( 1922 short_alias %(fragment_condition)s or 1923 firstnames %(fragment_condition)s or 1924 lastnames %(fragment_condition)s or 1925 db_user %(fragment_condition)s 1926 )""" 1927 ] 1928 ) 1929 self.setThresholds(1, 2, 3)
1930 #============================================================ 1931 # convenience functions 1932 #============================================================
1933 -def create_name(pk_person, firstnames, lastnames, active=False):
1934 queries = [{ 1935 'cmd': u"select dem.add_name(%s, %s, %s, %s)", 1936 'args': [pk_person, firstnames, lastnames, active] 1937 }] 1938 rows, idx = gmPG2.run_rw_queries(queries=queries, return_data=True) 1939 name = cPersonName(aPK_obj = rows[0][0]) 1940 return name
1941 #============================================================
1942 -def create_identity(gender=None, dob=None, lastnames=None, firstnames=None):
1943 1944 cmd1 = u"""insert into dem.identity (gender, dob) values (%s, %s)""" 1945 1946 cmd2 = u""" 1947 insert into dem.names ( 1948 id_identity, lastnames, firstnames 1949 ) values ( 1950 currval('dem.identity_pk_seq'), coalesce(%s, 'xxxDEFAULTxxx'), coalesce(%s, 'xxxDEFAULTxxx') 1951 )""" 1952 1953 rows, idx = gmPG2.run_rw_queries ( 1954 queries = [ 1955 {'cmd': cmd1, 'args': [gender, dob]}, 1956 {'cmd': cmd2, 'args': [lastnames, firstnames]}, 1957 {'cmd': u"select currval('dem.identity_pk_seq')"} 1958 ], 1959 return_data = True 1960 ) 1961 return cIdentity(aPK_obj=rows[0][0])
1962 #============================================================
1963 -def create_dummy_identity():
1964 cmd1 = u"insert into dem.identity(gender) values('xxxDEFAULTxxx')" 1965 cmd2 = u"select currval('dem.identity_pk_seq')" 1966 1967 rows, idx = gmPG2.run_rw_queries ( 1968 queries = [ 1969 {'cmd': cmd1}, 1970 {'cmd': cmd2} 1971 ], 1972 return_data = True 1973 ) 1974 return gmDemographicRecord.cIdentity(aPK_obj = rows[0][0])
1975 #============================================================
1976 -def set_active_patient(patient=None, forced_reload=False):
1977 """Set active patient. 1978 1979 If patient is -1 the active patient will be UNset. 1980 """ 1981 if isinstance(patient, cPatient): 1982 pat = patient 1983 elif isinstance(patient, cIdentity): 1984 pat = cPatient(aPK_obj=patient['pk_identity']) 1985 elif isinstance(patient, cStaff): 1986 pat = cPatient(aPK_obj=patient['pk_identity']) 1987 elif isinstance(patient, gmCurrentPatient): 1988 pat = patient.patient 1989 elif patient == -1: 1990 pat = patient 1991 else: 1992 raise ValueError('<patient> must be either -1, cPatient, cStaff, cIdentity or gmCurrentPatient instance, is: %s' % patient) 1993 1994 # attempt to switch 1995 try: 1996 gmCurrentPatient(patient = pat, forced_reload = forced_reload) 1997 except: 1998 _log.exception('error changing active patient to [%s]' % patient) 1999 return False 2000 2001 return True
2002 #------------------------------------------------------------
2003 -def prompted_input(prompt, default=None):
2004 """Obtains entry from standard input. 2005 2006 prompt - Prompt text to display in standard output 2007 default - Default value (for user to press enter only) 2008 """ 2009 msg = '%s (CTRL-C aborts) [%s]: ' % (prompt, default) 2010 try: 2011 usr_input = raw_input(msg) 2012 except KeyboardInterrupt: 2013 return None 2014 if usr_input == '': 2015 return default 2016 return usr_input
2017 #------------------------------------------------------------
2018 -def ask_for_patient():
2019 """Text mode UI function to ask for patient.""" 2020 2021 person_searcher = cPatientSearcher_SQL() 2022 2023 while True: 2024 search_fragment = prompted_input("\nEnter person search term or leave blank to exit") 2025 2026 if search_fragment in ['exit', 'quit', 'bye', None]: 2027 print "user cancelled patient search" 2028 return None 2029 2030 pats = person_searcher.get_patients(search_term = search_fragment) 2031 2032 if (pats is None) or (len(pats) == 0): 2033 print "No patient matches the query term." 2034 print "" 2035 continue 2036 2037 if len(pats) > 1: 2038 print "Several patients match the query term:" 2039 print "" 2040 for pat in pats: 2041 print pat 2042 print "" 2043 continue 2044 2045 return pats[0] 2046 2047 return None
2048 #============================================================ 2049 # gender related 2050 #------------------------------------------------------------
2051 -def get_gender_list():
2052 """Retrieves the list of known genders from the database.""" 2053 global __gender_idx 2054 global __gender_list 2055 2056 if __gender_list is None: 2057 cmd = u"select tag, l10n_tag, label, l10n_label, sort_weight from dem.v_gender_labels order by sort_weight desc" 2058 __gender_list, __gender_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 2059 2060 return (__gender_list, __gender_idx)
2061 #------------------------------------------------------------ 2062 map_gender2mf = { 2063 'm': u'm', 2064 'f': u'f', 2065 'tf': u'f', 2066 'tm': u'm', 2067 'h': u'mf' 2068 } 2069 #------------------------------------------------------------ 2070 # Maps GNUmed related i18n-aware gender specifiers to a unicode symbol. 2071 map_gender2symbol = { 2072 'm': u'\u2642', 2073 'f': u'\u2640', 2074 'tf': u'\u26A5\u2640', 2075 'tm': u'\u26A5\u2642', 2076 'h': u'\u26A5' 2077 # 'tf': u'\u2642\u2640-\u2640', 2078 # 'tm': u'\u2642\u2640-\u2642', 2079 # 'h': u'\u2642\u2640' 2080 } 2081 #------------------------------------------------------------
2082 -def map_gender2salutation(gender=None):
2083 """Maps GNUmed related i18n-aware gender specifiers to a human-readable salutation.""" 2084 2085 global __gender2salutation_map 2086 2087 if __gender2salutation_map is None: 2088 genders, idx = get_gender_list() 2089 __gender2salutation_map = { 2090 'm': _('Mr'), 2091 'f': _('Mrs'), 2092 'tf': u'', 2093 'tm': u'', 2094 'h': u'' 2095 } 2096 for g in genders: 2097 __gender2salutation_map[g[idx['l10n_tag']]] = __gender2salutation_map[g[idx['tag']]] 2098 __gender2salutation_map[g[idx['label']]] = __gender2salutation_map[g[idx['tag']]] 2099 __gender2salutation_map[g[idx['l10n_label']]] = __gender2salutation_map[g[idx['tag']]] 2100 2101 return __gender2salutation_map[gender]
2102 #------------------------------------------------------------
2103 -def map_firstnames2gender(firstnames=None):
2104 """Try getting the gender for the given first name.""" 2105 2106 if firstnames is None: 2107 return None 2108 2109 rows, idx = gmPG2.run_ro_queries(queries = [{ 2110 'cmd': u"select gender from dem.name_gender_map where name ilike %(fn)s limit 1", 2111 'args': {'fn': firstnames} 2112 }]) 2113 2114 if len(rows) == 0: 2115 return None 2116 2117 return rows[0][0]
2118 #============================================================
2119 -def get_staff_list(active_only=False):
2120 if active_only: 2121 cmd = u"select * from dem.v_staff where is_active order by can_login desc, short_alias asc" 2122 else: 2123 cmd = u"select * from dem.v_staff order by can_login desc, is_active desc, short_alias asc" 2124 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True) 2125 staff_list = [] 2126 for row in rows: 2127 obj_row = { 2128 'idx': idx, 2129 'data': row, 2130 'pk_field': 'pk_staff' 2131 } 2132 staff_list.append(cStaff(row=obj_row)) 2133 return staff_list
2134 #============================================================
2135 -def get_persons_from_pks(pks=None):
2136 return [ cIdentity(aPK_obj = pk) for pk in pks ]
2137 #============================================================
2138 -def get_person_from_xdt(filename=None, encoding=None, dob_format=None):
2139 from Gnumed.business import gmXdtObjects 2140 return gmXdtObjects.read_person_from_xdt(filename=filename, encoding=encoding, dob_format=dob_format)
2141 #============================================================
2142 -def get_persons_from_pracsoft_file(filename=None, encoding='ascii'):
2143 from Gnumed.business import gmPracSoftAU 2144 return gmPracSoftAU.read_persons_from_pracsoft_file(filename=filename, encoding=encoding)
2145 #============================================================ 2146 # main/testing 2147 #============================================================ 2148 if __name__ == '__main__': 2149 2150 import datetime 2151 2152 gmI18N.activate_locale() 2153 gmI18N.install_domain() 2154 gmDateTime.init() 2155 2156 #--------------------------------------------------------
2157 - def test_set_active_pat():
2158 2159 ident = cIdentity(1) 2160 print "setting active patient with", ident 2161 set_active_patient(patient=ident) 2162 2163 patient = cPatient(12) 2164 print "setting active patient with", patient 2165 set_active_patient(patient=patient) 2166 2167 pat = gmCurrentPatient() 2168 print pat['dob'] 2169 #pat['dob'] = 'test' 2170 2171 staff = cStaff() 2172 print "setting active patient with", staff 2173 set_active_patient(patient=staff) 2174 2175 print "setting active patient with -1" 2176 set_active_patient(patient=-1)
2177 #--------------------------------------------------------
2178 - def test_dto_person():
2179 dto = cDTO_person() 2180 dto.firstnames = 'Sepp' 2181 dto.lastnames = 'Herberger' 2182 dto.gender = 'male' 2183 dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone) 2184 print dto 2185 2186 print dto['firstnames'] 2187 print dto['lastnames'] 2188 print dto['gender'] 2189 print dto['dob'] 2190 2191 for key in dto.keys(): 2192 print key
2193 #--------------------------------------------------------
2194 - def test_search_by_dto():
2195 dto = cDTO_person() 2196 dto.firstnames = 'Sigrid' 2197 dto.lastnames = 'Kiesewetter' 2198 dto.gender = 'female' 2199 # dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone) 2200 dto.dob = datetime.datetime(1939,6,24,23,0,0,0,gmDateTime.gmCurrentLocalTimezone) 2201 print dto 2202 2203 searcher = cPatientSearcher_SQL() 2204 pats = searcher.get_patients(dto = dto) 2205 print pats
2206 2207 #--------------------------------------------------------
2208 - def test_staff():
2209 staff = cStaff() 2210 print staff 2211 print staff.inbox 2212 print staff.inbox.messages
2213 2214 #--------------------------------------------------------
2215 - def test_current_provider():
2216 staff = cStaff() 2217 provider = gmCurrentProvider(provider = staff) 2218 print provider 2219 print provider.inbox 2220 print provider.inbox.messages 2221 print provider.database_language 2222 tmp = provider.database_language 2223 provider.database_language = None 2224 print provider.database_language 2225 provider.database_language = tmp 2226 print provider.database_language
2227 #--------------------------------------------------------
2228 - def test_identity():
2229 # create patient 2230 print '\n\nCreating identity...' 2231 new_identity = create_identity(gender='m', dob='2005-01-01', lastnames='test lastnames', firstnames='test firstnames') 2232 print 'Identity created: %s' % new_identity 2233 2234 print '\nSetting title and gender...' 2235 new_identity['title'] = 'test title'; 2236 new_identity['gender'] = 'f'; 2237 new_identity.save_payload() 2238 print 'Refetching identity from db: %s' % cIdentity(aPK_obj=new_identity['pk_identity']) 2239 2240 print '\nGetting all names...' 2241 for a_name in new_identity.get_names(): 2242 print a_name 2243 print 'Active name: %s' % (new_identity.get_active_name()) 2244 print 'Setting nickname...' 2245 new_identity.set_nickname(nickname='test nickname') 2246 print 'Refetching all names...' 2247 for a_name in new_identity.get_names(): 2248 print a_name 2249 print 'Active name: %s' % (new_identity.get_active_name()) 2250 2251 print '\nIdentity occupations: %s' % new_identity['occupations'] 2252 print 'Creating identity occupation...' 2253 new_identity.link_occupation('test occupation') 2254 print 'Identity occupations: %s' % new_identity['occupations'] 2255 2256 print '\nIdentity addresses: %s' % new_identity.get_addresses() 2257 print 'Creating identity address...' 2258 # make sure the state exists in the backend 2259 new_identity.link_address ( 2260 number = 'test 1234', 2261 street = 'test street', 2262 postcode = 'test postcode', 2263 urb = 'test urb', 2264 state = 'SN', 2265 country = 'DE' 2266 ) 2267 print 'Identity addresses: %s' % new_identity.get_addresses() 2268 2269 print '\nIdentity communications: %s' % new_identity.get_comm_channels() 2270 print 'Creating identity communication...' 2271 new_identity.link_comm_channel('homephone', '1234566') 2272 print 'Identity communications: %s' % new_identity.get_comm_channels()
2273 #--------------------------------------------------------
2274 - def test_patient_search_queries():
2275 searcher = cPatientSearcher_SQL() 2276 2277 print "testing _normalize_soundalikes()" 2278 print "--------------------------------" 2279 # FIXME: support Ähler -> Äler and Dähler -> Däler 2280 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'] 2281 for name in data: 2282 print '%s: %s' % (name, searcher._normalize_soundalikes(name)) 2283 2284 raw_input('press [ENTER] to continue') 2285 print "============" 2286 2287 print "testing _generate_simple_query()" 2288 print "----------------------------" 2289 data = ['51234', '1 134 153', '#13 41 34', '#3-AFY322.4', '22-04-1906', '1235/32/3525', ' , johnny'] 2290 for fragment in data: 2291 print "fragment:", fragment 2292 qs = searcher._generate_simple_query(fragment) 2293 for q in qs: 2294 print " match on:", q['args'][0] 2295 print " query :", q['cmd'] 2296 raw_input('press [ENTER] to continue') 2297 print "============" 2298 2299 print "testing _generate_queries_from_dto()" 2300 print "------------------------------------" 2301 dto = cDTO_person() 2302 dto.gender = 'm' 2303 dto.lastnames = 'Kirk' 2304 dto.firstnames = 'James' 2305 dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone) 2306 q = searcher._generate_queries_from_dto(dto)[0] 2307 print "dto:", dto 2308 print " match on:", q['args'][0] 2309 print " query:", q['cmd'] 2310 2311 raw_input('press [ENTER] to continue') 2312 print "============" 2313 2314 print "testing _generate_queries_de()" 2315 print "------------------------------" 2316 qs = searcher._generate_queries_de('Kirk, James') 2317 for q in qs: 2318 print " match on:", q['args'][0] 2319 print " query :", q['cmd'] 2320 print " args :", q['args'] 2321 raw_input('press [ENTER] to continue') 2322 print "============" 2323 2324 qs = searcher._generate_queries_de(u'müller') 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'özdemir') 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 print "testing _generate_dumb_brute_query()" 2349 print "------------------------------------" 2350 q = searcher._generate_dumb_brute_query('Kirk, James Tiberius') 2351 print " match on:", q['args'][0] 2352 print " query:", q['cmd'] 2353 print " args:", q['args'] 2354 2355 raw_input('press [ENTER] to continue')
2356 #--------------------------------------------------------
2357 - def test_ask_for_patient():
2358 while 1: 2359 myPatient = ask_for_patient() 2360 if myPatient is None: 2361 break 2362 print "ID ", myPatient.ID 2363 print "names ", myPatient.get_names() 2364 print "addresses:", myPatient.get_addresses(address_type='home') 2365 print "recent birthday:", myPatient.dob_in_range() 2366 myPatient.export_as_gdt(filename='apw.gdt', encoding = 'cp850')
2367 # docs = myPatient.get_document_folder() 2368 # print "docs ", docs 2369 # emr = myPatient.get_emr() 2370 # print "EMR ", emr 2371 #--------------------------------------------------------
2372 - def test_name():
2373 for pk in range(1,16): 2374 name = cPersonName(aPK_obj=pk) 2375 print name.description 2376 print ' ', name
2377 #-------------------------------------------------------- 2378 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'): 2379 #test_patient_search_queries() 2380 #test_ask_for_patient() 2381 #test_dto_person() 2382 #test_identity() 2383 #test_set_active_pat() 2384 #test_search_by_dto() 2385 #test_staff() 2386 test_current_provider() 2387 #test_name() 2388 2389 #map_gender2salutation('m') 2390 2391 # module functions 2392 #genders, idx = get_gender_list() 2393 #print "\n\nRetrieving gender enum (tag, label, weight):" 2394 #for gender in genders: 2395 # print "%s, %s, %s" % (gender[idx['tag']], gender[idx['l10n_label']], gender[idx['sort_weight']]) 2396 2397 #comms = get_comm_list() 2398 #print "\n\nRetrieving communication media enum (id, description): %s" % comms 2399 2400 #============================================================ 2401 # $Log: gmPerson.py,v $ 2402 # Revision 1.198 2010-01-31 16:35:03 ncq 2403 # - put nick in "" 2404 # 2405 # Revision 1.197 2010/01/11 19:43:05 ncq 2406 # - do not change the patient if any of the 2407 # synchronous pre-selection callbacks fails 2408 # 2409 # Revision 1.196 2010/01/08 14:38:06 ncq 2410 # - support NULLing the dob 2411 # 2412 # Revision 1.195 2010/01/08 13:50:45 ncq 2413 # - enhance add-external-id() with pk-type 2414 # 2415 # Revision 1.194 2009/12/21 20:26:40 ncq 2416 # - some cleanup 2417 # 2418 # Revision 1.193 2009/12/21 14:59:17 ncq 2419 # - typo 2420 # 2421 # Revision 1.192 2009/11/30 22:24:16 ncq 2422 # - cleanup 2423 # 2424 # Revision 1.191 2009/11/13 21:04:12 ncq 2425 # - get-persons-from-pks 2426 # 2427 # Revision 1.190 2009/09/01 22:21:31 ncq 2428 # - nullify empty strings where appropriate 2429 # 2430 # Revision 1.189 2009/08/24 20:05:14 ncq 2431 # - use new cInboxMessage class 2432 # 2433 # Revision 1.188 2009/07/15 12:46:59 ncq 2434 # - support deceased 2435 # 2436 # Revision 1.187 2009/06/17 20:42:25 ncq 2437 # - is_patient property 2438 # 2439 # Revision 1.186 2009/06/04 16:24:13 ncq 2440 # - adjust to dob-less persons 2441 # 2442 # Revision 1.185 2009/04/24 12:04:44 ncq 2443 # - cleanup 2444 # 2445 # Revision 1.184 2009/04/21 16:54:04 ncq 2446 # - cleanup 2447 # 2448 # Revision 1.183 2009/04/03 09:32:01 ncq 2449 # - improved docs 2450 # 2451 # Revision 1.182 2009/02/25 21:05:36 ncq 2452 # - cleanup 2453 # 2454 # Revision 1.181 2009/02/25 09:49:49 ncq 2455 # - fix provider matcher to exlude inactive providers 2456 # 2457 # Revision 1.180 2009/02/20 15:42:08 ncq 2458 # - putting first patient on waiting list needs more care 2459 # 2460 # Revision 1.179 2009/02/10 18:37:36 ncq 2461 # - typo when deleting comm channel 2462 # 2463 # Revision 1.178 2009/01/30 12:08:20 ncq 2464 # - support zone in put_on_waiting_list 2465 # 2466 # Revision 1.177 2009/01/21 18:52:34 ncq 2467 # - signals cleanup 2468 # 2469 # Revision 1.176 2009/01/21 17:59:57 ncq 2470 # - improved logging 2471 # 2472 # Revision 1.175 2009/01/17 23:00:51 ncq 2473 # - put_on_waiting_list 2474 # 2475 # Revision 1.174 2009/01/02 11:36:18 ncq 2476 # - slightly reorder code for class dependancy clarity 2477 # - property database_language on staff 2478 # - raise AttributeError on faulty concurrent get_emr 2479 # 2480 # Revision 1.173 2008/12/25 16:52:41 ncq 2481 # - cleanup 2482 # - support .database_language on cStaff 2483 # 2484 # Revision 1.172 2008/12/22 18:58:02 ncq 2485 # - start supporting .tob 2486 # 2487 # Revision 1.171 2008/12/17 21:52:36 ncq 2488 # - add assimilation 2489 # 2490 # Revision 1.170 2008/12/09 23:19:47 ncq 2491 # - attribute description vs description_gender 2492 # 2493 # Revision 1.169 2008/11/23 12:42:57 ncq 2494 # - no more dummy names 2495 # 2496 # Revision 1.168 2008/11/21 13:03:36 ncq 2497 # - do not return a dummy name anymore 2498 # 2499 # Revision 1.167 2008/10/22 12:05:22 ncq 2500 # - improved logging of staff instantiation 2501 # 2502 # Revision 1.166 2008/10/12 15:15:07 ncq 2503 # - cleanup 2504 # - better exception wording 2505 # 2506 # Revision 1.165 2008/08/28 18:30:50 ncq 2507 # - cleanup 2508 # 2509 # Revision 1.164 2008/07/10 11:16:01 ncq 2510 # - make pre-selection callback failure more obvious 2511 # 2512 # Revision 1.163 2008/07/07 13:38:43 ncq 2513 # - is_connected -> connected property 2514 # - add in-sync pre-selection callbacks 2515 # 2516 # Revision 1.162 2008/06/28 18:24:24 ncq 2517 # - fix provider match provider to act on cursor-down / *, too 2518 # 2519 # Revision 1.161 2008/04/26 21:30:35 ncq 2520 # - fix unlink_occupation 2521 # 2522 # Revision 1.160 2008/04/02 10:15:17 ncq 2523 # - add missing s 2524 # 2525 # Revision 1.159 2008/03/09 20:13:47 ncq 2526 # - cleanup 2527 # 2528 # Revision 1.158 2008/02/25 17:31:41 ncq 2529 # - logging cleanup 2530 # 2531 # Revision 1.157 2008/01/30 13:34:50 ncq 2532 # - switch to std lib logging 2533 # 2534 # Revision 1.156 2008/01/27 21:08:32 ncq 2535 # - format_medical_age() improved 2536 # - map gender to unicode symbol 2537 # 2538 # Revision 1.155 2008/01/22 11:50:49 ncq 2539 # - cPersonName.description property aligned with cIdentity.get_description() 2540 # - test cPersonName 2541 # 2542 # Revision 1.154 2008/01/14 20:26:10 ncq 2543 # - better log 2544 # 2545 # Revision 1.153 2008/01/11 16:08:08 ncq 2546 # - first/last -> first-/lastnames 2547 # 2548 # Revision 1.152 2008/01/07 19:44:16 ncq 2549 # - use comm channel API 2550 # 2551 # Revision 1.151 2008/01/06 08:09:38 ncq 2552 # - in patient search by several means weed out duplicate finds 2553 # 2554 # Revision 1.150 2007/12/26 12:35:54 ncq 2555 # - cleanup 2556 # 2557 # Revision 1.149 2007/12/24 23:25:39 shilbert 2558 # - fix missing *args, **kwargs in import_extra_data 2559 # 2560 # Revision 1.148 2007/12/23 11:55:21 ncq 2561 # - cleanup 2562 # 2563 # Revision 1.147 2007/12/11 12:59:11 ncq 2564 # - cleanup and explicit signal handling 2565 # 2566 # Revision 1.146 2007/12/06 10:43:31 ncq 2567 # - fix typo 2568 # 2569 # Revision 1.145 2007/12/06 08:39:02 ncq 2570 # - add documentation on external IDs 2571 # - delete_external_id() 2572 # - edit_external_id() -> update_external_id() 2573 # 2574 # Revision 1.144 2007/12/03 20:42:37 ncq 2575 # - .delete_name() 2576 # - remove get_comm_list() 2577 # 2578 # Revision 1.143 2007/12/02 20:58:06 ncq 2579 # - adjust to table changes 2580 # - fix link_comm_channel() 2581 # 2582 # Revision 1.142 2007/11/28 22:35:03 ncq 2583 # - streamline get_description() 2584 # - make current patient listen on its identity/name tables 2585 # 2586 # Revision 1.141 2007/11/28 13:57:45 ncq 2587 # - fix SQL of cPersonName 2588 # 2589 # Revision 1.140 2007/11/28 11:51:48 ncq 2590 # - cPersonName 2591 # - cIdentity: 2592 # - check dob at __setitem__ level 2593 # - get_all_names() -> get_names(), remove cache 2594 # - use dem.v_person_names 2595 # - fix add_name() 2596 # - create_name() 2597 # 2598 # Revision 1.139 2007/11/17 16:11:42 ncq 2599 # - improve link_address() 2600 # - unlink_address() 2601 # 2602 # Revision 1.138 2007/11/12 22:56:34 ncq 2603 # - add missing '' around match_type 2604 # - get_external_ids() and use in export_as_gdt() 2605 # - add_external_id() 2606 # 2607 # Revision 1.137 2007/11/10 21:00:52 ncq 2608 # - implement dto.get_candidate_identities() and dto.import_into_database() 2609 # - stub dto.delete_from_source() 2610 # 2611 # Revision 1.136 2007/10/30 12:46:21 ncq 2612 # - test on "test" 2613 # 2614 # Revision 1.135 2007/10/30 12:43:42 ncq 2615 # - make inbox a property on cStaff 2616 # - teach gmCurrentProvider about __getattr__ 2617 # - improved testing 2618 # 2619 # Revision 1.134 2007/10/23 21:20:23 ncq 2620 # - cleanup 2621 # 2622 # Revision 1.133 2007/10/21 20:54:51 ncq 2623 # - add test case 2624 # 2625 # Revision 1.132 2007/10/09 11:22:05 ncq 2626 # - explicit casts for a whole bunch of queries 2627 # 2628 # Revision 1.131 2007/10/07 12:28:09 ncq 2629 # - workplace property now on gmSurgery.gmCurrentPractice() borg 2630 # 2631 # Revision 1.130 2007/09/24 22:08:56 ncq 2632 # - table-qualify ambigous column defs 2633 # 2634 # Revision 1.129 2007/09/10 12:34:02 ncq 2635 # - fix dob_in_range() 2636 # 2637 # Revision 1.128 2007/08/07 21:34:18 ncq 2638 # - cPaths -> gmPaths 2639 # 2640 # Revision 1.127 2007/07/17 21:43:29 ncq 2641 # - refcount patient lock 2642 # 2643 # Revision 1.126 2007/07/11 21:04:08 ncq 2644 # - make locked a property of gmCurrentPatient() 2645 # - improve ask_for_patient() 2646 # 2647 # Revision 1.125 2007/07/10 20:32:52 ncq 2648 # - return gmNull.cNull instance if gmCurrentProvider.patient is not connected 2649 # 2650 # Revision 1.124 2007/07/09 11:27:42 ncq 2651 # - put coalesce on dem.identity.title yet another time 2652 # 2653 # Revision 1.123 2007/06/28 12:31:34 ncq 2654 # - allow None for dob in dto 2655 # - set external ID to GNUmed interal ID on export_as_gdt() 2656 # - create proper queries from DTO in absence of, say, DOB 2657 # 2658 # Revision 1.122 2007/06/10 09:32:23 ncq 2659 # - cast "re" as "regex" 2660 # - use gmTools.capitalize() instead of homegrown _make_sane_caps() 2661 # - lots of u''ification in replace() 2662 # - improved query generation logging 2663 # - regex.match()/search() need u'' in the pattern or it 2664 # won't match anything in u'' strings, also set flags to 2665 # LOCALE/UNICODE 2666 # - use lower() on ~* queries since even PG 8.2 doesn't properly 2667 # support ~* with Umlauts :-(( 2668 # - improved test suite 2669 # 2670 # Revision 1.121 2007/05/21 22:29:18 ncq 2671 # - be more careful in link_occupation() 2672 # 2673 # Revision 1.120 2007/05/21 14:46:09 ncq 2674 # - cIdentity.get_dirname() 2675 # 2676 # Revision 1.119 2007/05/19 22:16:23 ncq 2677 # - a lot of cleanup/remomve _subtable stuff 2678 # - add __setitem__ to gmCurrentPatient 2679 # 2680 # Revision 1.118 2007/05/14 11:03:28 ncq 2681 # - latin1 -> utf8 2682 # 2683 # Revision 1.117 2007/05/11 14:10:52 ncq 2684 # - look in --conf-file for workplace def, too 2685 # 2686 # Revision 1.116 2007/05/07 12:29:02 ncq 2687 # - improve logic when looking for config file for workplace detection 2688 # 2689 # Revision 1.115 2007/05/07 08:00:18 ncq 2690 # - call get_emr() early enough 2691 # 2692 # Revision 1.114 2007/04/19 13:09:03 ncq 2693 # - read workplace from proper config file 2694 # 2695 # Revision 1.113 2007/04/06 23:14:24 ncq 2696 # - if we del the emr object link cache too early we'll get 2697 # a continue-encounter ? popup 2698 # 2699 # Revision 1.112 2007/03/18 13:04:42 ncq 2700 # - re-add lost 1.112 2701 # 2702 # Revision 1.112 2007/03/12 13:29:17 ncq 2703 # - add patient ID source in a smarter way 2704 # 2705 # Revision 1.111 2007/03/10 15:12:06 ncq 2706 # - export a dummy APW ID into the GDT file for demonstration 2707 # 2708 # Revision 1.110 2007/03/09 16:57:12 ncq 2709 # - prepare export_as_gdt() for use of pending-completion get_external_ids() 2710 # 2711 # Revision 1.109 2007/03/01 14:02:09 ncq 2712 # - support line length in export_as_gdt() :-( 2713 # 2714 # Revision 1.108 2007/02/22 22:38:56 ncq 2715 # - fix gdt field "names" 2716 # 2717 # Revision 1.107 2007/02/22 16:31:38 ncq 2718 # - u''ification 2719 # - massive cleanup/simplification 2720 # - cPatient/cStaff now cIdentity child 2721 # - remove cPerson 2722 # - make ID a property of cIdentity 2723 # - shadowing self._payload[self._idx['pk_identity']] 2724 # - so, no setting, only getting it, setting will raise Exception 2725 # - cIdentity.export_as_gdt() 2726 # - fix test suite so all tests pass again 2727 # 2728 # Revision 1.106 2007/02/19 16:45:21 ncq 2729 # - make DOB queries use dem.date_trunc_utc() 2730 # 2731 # Revision 1.105 2007/02/17 13:57:07 ncq 2732 # - cIdentity.dob_in_range() plus test 2733 # - make gmCurrentProvider.workplace an efficient property 2734 # 2735 # Revision 1.104 2007/02/15 14:56:53 ncq 2736 # - remove _() from ValueError() call 2737 # - map_firstnames2gender() 2738 # 2739 # Revision 1.103 2007/02/13 17:05:07 ncq 2740 # - add get_persons_from_pracsoft_file() 2741 # 2742 # Revision 1.102 2007/02/10 00:07:47 ncq 2743 # - ween out duplicate queries on getting patients 2744 # 2745 # Revision 1.101 2007/02/05 16:09:44 ncq 2746 # - fix person dto 2747 # 2748 # Revision 1.100 2007/01/16 17:58:11 ncq 2749 # -cleanup 2750 # 2751 # Revision 1.99 2007/01/16 14:23:24 ncq 2752 # - use current local time zone for now() in medical age calculation 2753 # 2754 # Revision 1.98 2007/01/16 12:08:29 ncq 2755 # - move dto.dob to datetime.datetime 2756 # 2757 # Revision 1.97 2007/01/15 13:01:19 ncq 2758 # - make dob queries cast dob literal to timestamp with time zone as it ought to be 2759 # - support dob_format in get_person_from_xdt() 2760 # 2761 # Revision 1.96 2006/12/13 13:43:10 ncq 2762 # - cleanup 2763 # 2764 # Revision 1.95 2006/11/27 12:37:09 ncq 2765 # - do not display 12y0m but rather 12y in format_age_medically() 2766 # 2767 # Revision 1.94 2006/11/24 09:33:22 ncq 2768 # - remove comms subtable 2769 # - chain cPerson.__getitem__ to underlying cIdentity where necessary 2770 # - fix no-cfg-file detection in get_workplace() 2771 # - add format_age_medically() and use it 2772 # 2773 # Revision 1.93 2006/11/20 19:10:39 ncq 2774 # - more consistent method names 2775 # - raise instead of return None where appropriate 2776 # - improved logging 2777 # - _generate_dumb_brute_query() returned wrong type 2778 # 2779 # Revision 1.92 2006/11/20 15:57:16 ncq 2780 # - fix (un)link_occupation when occupation/activies=None 2781 # - add get_comm_channels() 2782 # 2783 # Revision 1.91 2006/11/19 11:02:33 ncq 2784 # - remove subtable defs, add corresponding APIs 2785 # 2786 # Revision 1.90 2006/11/09 17:46:04 ncq 2787 # - raise exception if dob is about to be set without a timezone 2788 # 2789 # Revision 1.89 2006/11/07 23:43:34 ncq 2790 # - cIdentity now requires datetime.datetime as DOB 2791 # - fix dob2medical_age() 2792 # 2793 # Revision 1.88 2006/11/06 09:58:11 ncq 2794 # - add missing continue in get_identities() 2795 # 2796 # Revision 1.87 2006/11/05 16:01:24 ncq 2797 # - include nick in identity description string, user wants to 2798 # abuse it for other means 2799 # 2800 # Revision 1.86 2006/11/01 12:54:03 ncq 2801 # - return None from get_last_encounter() if there is none, that's the whole point ! 2802 # - fix patient search queries: select distinct on level above order by 2803 # so pk_identity does not have to be first order by parameter 2804 # 2805 # Revision 1.85 2006/10/31 11:26:56 ncq 2806 # - dob2medical_age(): use datetime.datetime 2807 # 2808 # Revision 1.84 2006/10/28 14:52:07 ncq 2809 # - add get_last_encounter() 2810 # 2811 # Revision 1.83 2006/10/24 13:16:38 ncq 2812 # - add Provider match provider 2813 # 2814 # Revision 1.82 2006/10/21 20:44:06 ncq 2815 # - no longer import gmPG 2816 # - convert to gmPG2 2817 # - add __gender2salutation_map, map_gender2salutation() 2818 # - adjust to changes in gmBusinessDBObject 2819 # - fix patient searcher query generation 2820 # - improved test suite 2821 # 2822 # Revision 1.81 2006/09/13 07:53:26 ncq 2823 # - in get_person_from_xdt() handle encoding 2824 # 2825 # Revision 1.80 2006/07/26 12:22:56 ncq 2826 # - improve set_active_patient 2827 # 2828 # Revision 1.79 2006/07/24 14:16:04 ncq 2829 # - cleanup 2830 # 2831 # Revision 1.78 2006/07/17 21:06:12 ncq 2832 # - cleanup 2833 # 2834 # Revision 1.77 2006/07/17 18:49:07 ncq 2835 # - fix wrong naming 2836 # 2837 # Revision 1.76 2006/07/17 18:08:03 ncq 2838 # - add cDTO_person() 2839 # - add get_patient_from_xdt() 2840 # - fix __generate_queries_generic() 2841 # - cleanup, better testing 2842 # 2843 # Revision 1.75 2006/06/15 07:54:04 ncq 2844 # - allow editing of db_user in cStaff except where cStaff represents CURRENT_USER 2845 # 2846 # Revision 1.74 2006/06/14 10:22:46 ncq 2847 # - create_* stored procs are in schema dem.* now 2848 # 2849 # Revision 1.73 2006/06/12 18:28:32 ncq 2850 # - added missing raise in gmCurrentPatient.__init__() 2851 # 2852 # Revision 1.72 2006/06/09 14:38:42 ncq 2853 # - sort result of get_staff_list() 2854 # 2855 # Revision 1.71 2006/06/06 20:47:39 ncq 2856 # - add is_active to staff class 2857 # - add get_staff_list() 2858 # 2859 # Revision 1.70 2006/05/25 22:12:50 ncq 2860 # - self._patient -> self.patient to be more pythonic 2861 # 2862 # Revision 1.69 2006/05/25 12:07:29 sjtan 2863 # 2864 # base class method needs self object. 2865 # 2866 # Revision 1.68 2006/05/15 13:24:13 ncq 2867 # - signal "activating_patient" -> "pre_patient_selection" 2868 # - signal "patient_selected" -> "post_patient_selection" 2869 # 2870 # Revision 1.67 2006/05/14 21:44:22 ncq 2871 # - add get_workplace() to gmPerson.gmCurrentProvider and make use thereof 2872 # - remove use of gmWhoAmI.py 2873 # 2874 # Revision 1.66 2006/05/12 13:53:08 ncq 2875 # - lazy import gmClinicalRecord 2876 # 2877 # Revision 1.65 2006/05/12 12:03:55 ncq 2878 # - gmLoggedOnStaffMember -> gmCurrentProvider 2879 # 2880 # Revision 1.64 2006/05/10 21:15:58 ncq 2881 # - add current provider Borg 2882 # - add cStaff 2883 # 2884 # Revision 1.63 2006/05/04 09:59:35 ncq 2885 # - add cStaffMember(cPerson) 2886 # 2887 # Revision 1.62 2006/05/04 09:41:05 ncq 2888 # - cPerson 2889 # - factor out stuff for cPatient 2890 # - self.__ID -> self._ID for inheritance 2891 # - cPatient 2892 # - inherit from cPerson 2893 # - add patient specific methods 2894 # - deprecate get_clinical_record() over get_emr() 2895 # - cleanup doc folder instance on cleanup() 2896 # - gmCurrentPatient 2897 # - keyword change person -> patient 2898 # - accept cPatient instance 2899 # - self._person -> self._patient 2900 # - cPatientSearcher_SQL 2901 # - add get_patients() 2902 # - set_active_patient() 2903 # - raise ValueError on such 2904 # - ask_for_patient() 2905 # - improve breakout detection 2906 # - remove side effect of activating patient 2907 # - make "unit tests" work again 2908 # 2909 # Revision 1.61 2006/01/11 13:14:20 ncq 2910 # - id -> pk 2911 # 2912 # Revision 1.60 2006/01/07 13:13:46 ncq 2913 # - more schema qualifications 2914 # 2915 # Revision 1.59 2006/01/06 10:15:37 ncq 2916 # - lots of small fixes adjusting to "dem" schema 2917 # 2918 # Revision 1.58 2005/11/18 15:16:55 ncq 2919 # - run dumb, brute person search query on really complex search terms 2920 # 2921 # Revision 1.57 2005/11/13 15:28:06 ncq 2922 # - properly fix unicode problem when normalizing name search terms 2923 # 2924 # Revision 1.56 2005/10/09 12:22:54 ihaywood 2925 # new rich text 2926 # widget 2927 # bugfix to gmperson.py 2928 # 2929 # Revision 1.55 2005/09/25 01:00:47 ihaywood 2930 # bugfixes 2931 # 2932 # remember 2.6 uses "import wx" not "from wxPython import wx" 2933 # removed not null constraint on clin_encounter.rfe as has no value on instantiation 2934 # client doesn't try to set clin_encounter.description as it doesn't exist anymore 2935 # 2936 # Revision 1.54 2005/09/19 16:33:31 ncq 2937 # - less incorrect message re EMR loading 2938 # 2939 # Revision 1.53 2005/09/12 15:06:20 ncq 2940 # - add space after title 2941 # 2942 # Revision 1.52 2005/09/11 17:25:31 ncq 2943 # - support force_reload in gmCurrentPatient - needed since Richard wants to 2944 # reload data when finding the same patient again 2945 # 2946 # Revision 1.51 2005/08/08 08:06:44 ncq 2947 # - cleanup 2948 # 2949 # Revision 1.50 2005/07/24 18:44:33 ncq 2950 # - actually, make it an outright error to stuff strings 2951 # into DateTime objects - as we can't know the format 2952 # we couldn't do much about it anyways ... callers better 2953 # do their part 2954 # 2955 # Revision 1.49 2005/07/24 18:38:42 ncq 2956 # - look out for strings being stuffed into datetime objects 2957 # 2958 # Revision 1.48 2005/06/04 09:30:08 ncq 2959 # - just silly whitespace cleanup 2960 # 2961 # Revision 1.47 2005/06/03 15:24:27 cfmoro 2962 # Fix to make lin_comm work. FIXME added 2963 # 2964 # Revision 1.46 2005/05/28 11:46:28 cfmoro 2965 # Evict cache in identity linking/add methods 2966 # 2967 # Revision 1.45 2005/05/23 12:01:07 cfmoro 2968 # Create/update comms 2969 # 2970 # Revision 1.44 2005/05/19 17:33:07 cfmoro 2971 # Minor fix 2972 # 2973 # Revision 1.43 2005/05/19 16:31:45 ncq 2974 # - handle state_code/country_code in identity.addresses subtable select 2975 # 2976 # Revision 1.42 2005/05/19 15:55:51 ncq 2977 # - de-escalated error level from Panic to Error on failing to add name/nickname 2978 # 2979 # Revision 1.41 2005/05/19 15:19:48 cfmoro 2980 # Minor fixes when object is None 2981 # 2982 # Revision 1.40 2005/05/18 08:27:14 cfmoro 2983 # link_communication failing becouse of situacion of add_to_subtable ( ? 2984 # 2985 # Revision 1.39 2005/05/17 18:01:19 ncq 2986 # - cleanup 2987 # 2988 # Revision 1.38 2005/05/17 14:41:36 cfmoro 2989 # Notebooked patient editor initial code 2990 # 2991 # Revision 1.37 2005/05/17 08:03:05 ncq 2992 # - fix unicode errors in DE query generator normalizer 2993 # 2994 # Revision 1.36 2005/05/14 15:06:18 ncq 2995 # - fix logging error 2996 # 2997 # Revision 1.35 2005/05/12 15:07:25 ncq 2998 # - add get_emr() 2999 # 3000 # Revision 1.34 2005/05/04 08:55:08 ncq 3001 # - streamlining 3002 # - comply with slightly changed subtables API 3003 # 3004 # Revision 1.33 2005/05/01 10:15:59 cfmoro 3005 # Link_XXX methods ported to take advantage of subtables framework. save_payload seems need fixing, as no values are dumped to backed 3006 # 3007 # Revision 1.32 2005/04/28 19:21:18 cfmoro 3008 # zip code streamlining 3009 # 3010 # Revision 1.31 2005/04/28 16:32:19 cfmoro 3011 # Leave town postcode out of linking an address 3012 # 3013 # Revision 1.30 2005/04/26 18:16:13 ncq 3014 # - cIdentity needs a cleanup() 3015 # 3016 # Revision 1.29 2005/04/23 08:48:52 cfmoro 3017 # Improved version of linking communications, controlling duplicates and medium in plpgsql 3018 # 3019 # Revision 1.28 2005/04/23 07:52:38 cfmoro 3020 # Added get_comm_list and cIdentity.link_communication methods 3021 # 3022 # Revision 1.27 2005/04/23 06:14:25 cfmoro 3023 # Added cIdentity.link_address method 3024 # 3025 # Revision 1.26 2005/04/20 21:55:39 ncq 3026 # - just some cleanup 3027 # 3028 # Revision 1.25 2005/04/19 19:51:49 cfmoro 3029 # Names cached in get_all_names. Added get_active_name 3030 # 3031 # Revision 1.24 2005/04/18 19:18:44 ncq 3032 # - cleanup, link_occuption doesn't work right yet 3033 # 3034 # Revision 1.23 2005/04/18 16:07:11 cfmoro 3035 # Improved sanity check in add_name 3036 # 3037 # Revision 1.22 2005/04/18 15:55:37 cfmoro 3038 # added set_nickname method, test code and minor update string fixes 3039 # 3040 # Revision 1.21 2005/04/14 22:34:50 ncq 3041 # - some streamlining of create_identity 3042 # 3043 # Revision 1.20 2005/04/14 19:27:20 cfmoro 3044 # Added title param to create_identity, to cover al fields in basic patient details 3045 # 3046 # Revision 1.19 2005/04/14 19:04:01 cfmoro 3047 # create_occupation -> add_occupation 3048 # 3049 # Revision 1.18 2005/04/14 18:58:14 cfmoro 3050 # Added create occupation method and minor gender map clean up, to replace later by get_gender_list 3051 # 3052 # Revision 1.17 2005/04/14 18:23:59 ncq 3053 # - get_gender_list() 3054 # 3055 # Revision 1.16 2005/04/14 08:51:13 ncq 3056 # - add cIdentity/dob2medical_age() from gmDemographicRecord.py 3057 # - make cIdentity inherit from cBusinessDBObject 3058 # - add create_identity() 3059 # 3060 # Revision 1.15 2005/03/20 16:49:07 ncq 3061 # - fix SQL syntax and do run all queries until identities found 3062 # - we now find Richard 3063 # - cleanup 3064 # 3065 # Revision 1.14 2005/03/18 07:44:10 ncq 3066 # - queries fixed but logic needs more work ! 3067 # 3068 # Revision 1.13 2005/03/16 12:57:26 sjtan 3069 # 3070 # fix import error. 3071 # 3072 # Revision 1.12 2005/03/08 16:43:58 ncq 3073 # - allow a cIdentity instance to be passed to gmCurrentPatient 3074 # 3075 # Revision 1.11 2005/02/19 15:06:33 sjtan 3076 # 3077 # **kwargs should be passed for signal parameters. 3078 # 3079 # Revision 1.10 2005/02/15 18:29:03 ncq 3080 # - test_result.id -> pk 3081 # 3082 # Revision 1.9 2005/02/13 15:23:31 ncq 3083 # - v_basic_person.i_pk -> pk_identity 3084 # 3085 # Revision 1.8 2005/02/12 13:50:25 ncq 3086 # - identity.id -> identity.pk and followup changes in v_basic_person 3087 # 3088 # Revision 1.7 2005/02/02 23:03:17 ncq 3089 # - change "demographic record" to "identity" 3090 # - dependant files still need being changed 3091 # 3092 # Revision 1.6 2005/02/01 19:27:56 ncq 3093 # - more renaming, I think we are getting there, if you think about it it 3094 # seems "demographic record" really is "identity" 3095 # 3096 # Revision 1.5 2005/02/01 19:14:10 ncq 3097 # - cleanup, internal renaming for consistency 3098 # - reallow cPerson to be instantiated with PK but retain main instantiation mode with cIdentity 3099 # - smarten up gmCurrentPatient() and re-add previous speedups 3100 # - do use ask_for_patient() in unit test 3101 # 3102 # Revision 1.4 2005/02/01 10:16:07 ihaywood 3103 # refactoring of gmDemographicRecord and follow-on changes as discussed. 3104 # 3105 # gmTopPanel moves to gmHorstSpace 3106 # gmRichardSpace added -- example code at present, haven't even run it myself 3107 # (waiting on some icon .pngs from Richard) 3108 # 3109 # Revision 1.3 2005/01/31 18:48:45 ncq 3110 # - self._patient -> self._person 3111 # - speedup 3112 # 3113 # Revision 1.2 2005/01/31 12:59:56 ncq 3114 # - cleanup, improved comments 3115 # - rename class gmPerson to cPerson 3116 # - add helpers prompted_input() and ask_for_patient() 3117 # 3118 # Revision 1.1 2005/01/31 10:24:17 ncq 3119 # - renamed from gmPatient.py 3120 # 3121 # Revision 1.56 2004/09/02 00:52:10 ncq 3122 # - wait, #digits may still be an external ID search so allow for that 3123 # 3124 # Revision 1.55 2004/09/02 00:37:49 ncq 3125 # - it's ~*, not *~ 3126 # 3127 # Revision 1.54 2004/09/01 21:57:55 ncq 3128 # - make search GnuMed primary key work 3129 # - add search for arbitrary external ID via "#..." 3130 # - fix regexing in __normalize() to avoid nested replacements 3131 # 3132 # Revision 1.53 2004/08/24 19:15:42 ncq 3133 # - __normalize_soundalikes() -> __normalize() + improve (apostrophy/hyphen) 3134 # 3135 # Revision 1.52 2004/08/24 14:27:06 ncq 3136 # - improve __normalize_soundalikes() 3137 # - fix nasty bug: missing ] resulting in endless logging 3138 # - prepare search on external id 3139 # 3140 # Revision 1.51 2004/08/20 13:28:16 ncq 3141 # - cleanup/improve inline docs 3142 # - allow gmCurrentPatient._patient to be reset to gmNull.cNull on aPKey = -1 3143 # - teach patient searcher about ", something" to be first name 3144 # 3145 # Revision 1.50 2004/08/18 08:13:51 ncq 3146 # - fixed encoding special comment 3147 # 3148 # Revision 1.49 2004/07/21 07:53:12 ncq 3149 # - some cleanup in set_active_patient 3150 # 3151 # Revision 1.48 2004/07/20 10:09:44 ncq 3152 # - a bit of cleanup here and there 3153 # - use Null design pattern instead of None when no real 3154 # patient connected to gmCurrentPatient Borg 3155 # 3156 # this allows us to forego all the tests for None as 3157 # Null() reliably does nothing no matter what you try, 3158 # eventually, this will allow us to remove all the 3159 # is_patient_avail checks in the frontend, 3160 # it also acts sanely for code forgetting to check 3161 # for a connected patient 3162 # 3163 # Revision 1.47 2004/07/20 01:01:44 ihaywood 3164 # changing a patients name works again. 3165 # Name searching has been changed to query on names rather than v_basic_person. 3166 # This is so the old (inactive) names are still visible to the search. 3167 # This is so when Mary Smith gets married, we can still find her under Smith. 3168 # [In Australia this odd tradition is still the norm, even female doctors 3169 # have their medical registration documents updated] 3170 # 3171 # SOAPTextCtrl now has popups, but the cursor vanishes (?) 3172 # 3173 # Revision 1.46 2004/07/15 23:30:11 ncq 3174 # - 'clinical_record' -> get_clinical_record() 3175 # 3176 # Revision 1.45 2004/07/05 22:26:24 ncq 3177 # - do some timings to find patient change time sinks 3178 # 3179 # Revision 1.44 2004/06/15 19:14:30 ncq 3180 # - add cleanup() to current patient calling gmPerson.cleanup() 3181 # 3182 # Revision 1.43 2004/06/01 23:58:01 ncq 3183 # - debugged dob handling in _make_queries_generic 3184 # 3185 # Revision 1.42 2004/06/01 07:50:56 ncq 3186 # - typo fix 3187 # 3188 # Revision 1.41 2004/05/18 22:38:19 ncq 3189 # - __patient -> _patient 3190 # 3191 # Revision 1.40 2004/05/18 20:40:11 ncq 3192 # - streamline __init__ significantly 3193 # - check return status of get_clinical_record() 3194 # - self.patient -> self.__patient 3195 # 3196 # Revision 1.39 2004/04/11 10:14:36 ncq 3197 # - fix b0rked dob/dod handling in query generation 3198 # - searching by dob should now work 3199 # 3200 # Revision 1.38 2004/03/25 11:14:48 ncq 3201 # - fix get_document_folder() 3202 # 3203 # Revision 1.37 2004/03/25 11:03:23 ncq 3204 # - getActiveName -> get_names 3205 # 3206 # Revision 1.36 2004/03/25 09:47:56 ncq 3207 # - fix whitespace breakage 3208 # 3209 # Revision 1.35 2004/03/23 15:04:59 ncq 3210 # - merge Carlos' constraints into get_text_dump 3211 # - add gmPatient.export_data() 3212 # 3213 # Revision 1.34 2004/03/20 19:44:50 ncq 3214 # - do import gmI18N 3215 # - only fetch i_id in queries 3216 # - revert to returning flat list of ids from get_patient_ids, must have been Syan fallout, I assume 3217 # 3218 # Revision 1.33 2004/03/20 13:31:18 ncq 3219 # - PostgreSQL has date_trunc, not datetrunc 3220 # 3221 # Revision 1.32 2004/03/20 13:14:36 ncq 3222 # - sync data dict and named substs in __generate_queries_generic 3223 # 3224 # Revision 1.31 2004/03/20 13:05:20 ncq 3225 # - we of course need to return results from __generate_queries_generic 3226 # 3227 # Revision 1.30 2004/03/20 12:49:55 ncq 3228 # - support gender, too, in search_dict in get_patient_ids 3229 # 3230 # Revision 1.29 2004/03/20 12:32:51 ncq 3231 # - check for query_lists is None in get_pat_ids 3232 # 3233 # Revision 1.28 2004/03/20 11:45:41 ncq 3234 # - don't pass search_dict[id] to get_patient_ids() 3235 # 3236 # Revision 1.27 2004/03/20 11:10:46 ncq 3237 # - where_snippets needs to be [] 3238 # 3239 # Revision 1.26 2004/03/20 10:48:31 ncq 3240 # - if search_dict given we need to pass it to run_ro_query 3241 # 3242 # Revision 1.25 2004/03/19 11:46:24 ncq 3243 # - add search_term to get_pat_ids() 3244 # 3245 # Revision 1.24 2004/03/10 13:44:33 ncq 3246 # - shouldn't just import gmI18N, needs fix, I guess 3247 # 3248 # Revision 1.23 2004/03/10 12:56:01 ihaywood 3249 # fixed sudden loss of main.shadow 3250 # more work on referrals, 3251 # 3252 # Revision 1.22 2004/03/10 00:09:51 ncq 3253 # - cleanup imports 3254 # 3255 # Revision 1.21 2004/03/09 07:34:51 ihaywood 3256 # reactivating plugins 3257 # 3258 # Revision 1.20 2004/03/07 23:52:32 ncq 3259 # - get_document_folder() 3260 # 3261 # Revision 1.19 2004/03/04 19:46:53 ncq 3262 # - switch to package based import: from Gnumed.foo import bar 3263 # 3264 # Revision 1.18 2004/02/25 09:46:20 ncq 3265 # - import from pycommon now, not python-common 3266 # 3267 # Revision 1.17 2004/02/18 06:36:04 ihaywood 3268 # bugfixes 3269 # 3270 # Revision 1.16 2004/02/17 10:38:27 ncq 3271 # - create_new_patient() -> xlnk_patient_in_clinical() 3272 # 3273 # Revision 1.15 2004/02/14 00:37:10 ihaywood 3274 # Bugfixes 3275 # - weeks = days / 7 3276 # - create_new_patient to maintain xlnk_identity in historica 3277 # 3278 # Revision 1.14 2004/02/05 18:38:56 ncq 3279 # - add .get_ID(), .is_locked() 3280 # - set_active_patient() convenience function 3281 # 3282 # Revision 1.13 2004/02/04 00:57:24 ncq 3283 # - added UI-independant patient search logic taken from gmPatientSelector 3284 # - we can now have a console patient search field just as powerful as 3285 # the GUI version due to it running the same business logic code 3286 # - also fixed _make_simple_query() results 3287 # 3288 # Revision 1.12 2004/01/18 21:43:00 ncq 3289 # - speed up get_clinical_record() 3290 # 3291 # Revision 1.11 2004/01/12 16:21:03 ncq 3292 # - _get_clini* -> get_clini* 3293 # 3294 # Revision 1.10 2003/11/20 01:17:14 ncq 3295 # - consensus was that N/A is no good for identity.gender hence 3296 # don't use it in create_dummy_identity anymore 3297 # 3298 # Revision 1.9 2003/11/18 16:35:17 ncq 3299 # - correct create_dummy_identity() 3300 # - move create_dummy_relative to gmDemographicRecord and rename it to link_new_relative 3301 # - remove reload keyword from gmCurrentPatient.__init__() - if you need it your logic 3302 # is screwed 3303 # 3304 # Revision 1.8 2003/11/17 10:56:34 sjtan 3305 # 3306 # synced and commiting. 3307 # 3308 # Revision 1.7 2003/11/16 10:58:36 shilbert 3309 # - corrected typo 3310 # 3311 # Revision 1.6 2003/11/09 16:39:34 ncq 3312 # - get handler now 'demographic record', not 'demographics' 3313 # 3314 # Revision 1.5 2003/11/04 00:07:40 ncq 3315 # - renamed gmDemographics 3316 # 3317 # Revision 1.4 2003/10/26 17:35:04 ncq 3318 # - conceptual cleanup 3319 # - IMHO, patient searching and database stub creation is OUTSIDE 3320 # THE SCOPE OF gmPerson and gmDemographicRecord 3321 # 3322 # Revision 1.3 2003/10/26 11:27:10 ihaywood 3323 # gmPatient is now the "patient stub", all demographics stuff in gmDemographics. 3324 # 3325 # Ergregious breakages are fixed, but needs more work 3326 # 3327 # Revision 1.2 2003/10/26 01:38:06 ncq 3328 # - gmTmpPatient -> gmPatient, cleanup 3329 # 3330 # Revision 1.1 2003/10/26 01:26:45 ncq 3331 # - now go live, this is for real 3332 # 3333 # Revision 1.41 2003/10/19 10:42:57 ihaywood 3334 # extra functions 3335 # 3336 # Revision 1.40 2003/09/24 08:45:40 ihaywood 3337 # NewAddress now functional 3338 # 3339 # Revision 1.39 2003/09/23 19:38:03 ncq 3340 # - cleanup 3341 # - moved GetAddressesType out of patient class - it's a generic function 3342 # 3343 # Revision 1.38 2003/09/23 12:49:56 ncq 3344 # - reformat, %d -> %s 3345 # 3346 # Revision 1.37 2003/09/23 12:09:26 ihaywood 3347 # Karsten, we've been tripping over each other again 3348 # 3349 # Revision 1.36 2003/09/23 11:31:12 ncq 3350 # - properly use ro_run_query()s returns 3351 # 3352 # Revision 1.35 2003/09/22 23:29:30 ncq 3353 # - new style run_ro_query() 3354 # 3355 # Revision 1.34 2003/09/21 12:46:30 ncq 3356 # - switched most ro queries to run_ro_query() 3357 # 3358 # Revision 1.33 2003/09/21 10:37:20 ncq 3359 # - bugfix, cleanup 3360 # 3361 # Revision 1.32 2003/09/21 06:53:40 ihaywood 3362 # bugfixes 3363 # 3364 # Revision 1.31 2003/09/17 11:08:30 ncq 3365 # - cleanup, fix type "personaliaa" 3366 # 3367 # Revision 1.30 2003/09/17 03:00:59 ihaywood 3368 # support for local inet connections 3369 # 3370 # Revision 1.29 2003/07/19 20:18:28 ncq 3371 # - code cleanup 3372 # - explicitely cleanup EMR when cleaning up patient 3373 # 3374 # Revision 1.28 2003/07/09 16:21:22 ncq 3375 # - better comments 3376 # 3377 # Revision 1.27 2003/06/27 16:04:40 ncq 3378 # - no ; in DB-API 3379 # 3380 # Revision 1.26 2003/06/26 21:28:02 ncq 3381 # - fatal->verbose, %s; quoting bug 3382 # 3383 # Revision 1.25 2003/06/22 16:18:34 ncq 3384 # - cleanup, send signal prior to changing the active patient, too 3385 # 3386 # Revision 1.24 2003/06/19 15:24:23 ncq 3387 # - add is_connected check to gmCurrentPatient to find 3388 # out whether there's a live patient record attached 3389 # - typo fix 3390 # 3391 # Revision 1.23 2003/06/01 14:34:47 sjtan 3392 # 3393 # hopefully complies with temporary model; not using setData now ( but that did work). 3394 # Please leave a working and tested substitute (i.e. select a patient , allergy list 3395 # will change; check allergy panel allows update of allergy list), if still 3396 # not satisfied. I need a working model-view connection ; trying to get at least 3397 # a basically database updating version going . 3398 # 3399 # Revision 1.22 2003/06/01 13:34:38 ncq 3400 # - reinstate remote app locking 3401 # - comment out thread lock for now but keep code 3402 # - setting gmCurrentPatient is not how it is supposed to work (I think) 3403 # 3404 # Revision 1.21 2003/06/01 13:20:32 sjtan 3405 # 3406 # logging to data stream for debugging. Adding DEBUG tags when work out how to use vi 3407 # with regular expression groups (maybe never). 3408 # 3409 # Revision 1.20 2003/06/01 01:47:32 sjtan 3410 # 3411 # starting allergy connections. 3412 # 3413 # Revision 1.19 2003/04/29 15:24:05 ncq 3414 # - add _get_clinical_record handler 3415 # - add _get_API API discovery handler 3416 # 3417 # Revision 1.18 2003/04/28 21:36:33 ncq 3418 # - compactify medical age 3419 # 3420 # Revision 1.17 2003/04/25 12:58:58 ncq 3421 # - dynamically handle supplied data in create_patient but added some sanity checks 3422 # 3423 # Revision 1.16 2003/04/19 22:54:46 ncq 3424 # - cleanup 3425 # 3426 # Revision 1.15 2003/04/19 14:59:04 ncq 3427 # - attribute handler for "medical age" 3428 # 3429 # Revision 1.14 2003/04/09 16:15:44 ncq 3430 # - get handler for age 3431 # 3432 # Revision 1.13 2003/04/04 20:40:51 ncq 3433 # - handle connection errors gracefully 3434 # - let gmCurrentPatient be a borg but let the person object be an attribute thereof 3435 # instead of an ancestor, this way we can safely do __init__(aPKey) where aPKey may or 3436 # may not be None 3437 # 3438 # Revision 1.12 2003/03/31 23:36:51 ncq 3439 # - adapt to changed view v_basic_person 3440 # 3441 # Revision 1.11 2003/03/27 21:08:25 ncq 3442 # - catch connection errors 3443 # - create_patient rewritten 3444 # - cleanup on __del__ 3445 # 3446 # Revision 1.10 2003/03/25 12:32:31 ncq 3447 # - create_patient helper 3448 # - __getTitle 3449 # 3450 # Revision 1.9 2003/02/21 16:42:02 ncq 3451 # - better error handling on query generation 3452 # 3453 # Revision 1.8 2003/02/18 02:41:54 ncq 3454 # - helper function get_patient_ids, only structured search term search implemented so far 3455 # 3456 # Revision 1.7 2003/02/17 16:16:13 ncq 3457 # - document list -> document id list 3458 # 3459 # Revision 1.6 2003/02/11 18:21:36 ncq 3460 # - move over to __getitem__ invoking handlers 3461 # - self.format to be used as an arbitrary format string 3462 # 3463 # Revision 1.5 2003/02/11 13:03:44 ncq 3464 # - don't change patient on patient not found ... 3465 # 3466 # Revision 1.4 2003/02/09 23:38:21 ncq 3467 # - now actually listens patient selectors, commits old patient and 3468 # inits the new one if possible 3469 # 3470 # Revision 1.3 2003/02/08 00:09:46 ncq 3471 # - finally starts being useful 3472 # 3473 # Revision 1.2 2003/02/06 15:40:58 ncq 3474 # - hit hard the merge wall 3475 # 3476 # Revision 1.1 2003/02/01 17:53:12 ncq 3477 # - doesn't do anything, just to show people where I am going 3478 # 3479