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