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