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