| Trees | Indices | Help |
|
|---|
|
|
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 #============================================================
35
36 # FIXME: make this work as a mapping type, too
37
38 #--------------------------------------------------------
39 # external API
40 #--------------------------------------------------------
43 #--------------------------------------------------------
46 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
122 #--------------------------------------------------------
123 # customizing behaviour
124 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
159 #============================================================
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #============================================================
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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
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 #--------------------------------------------------------
273 if self.__inbox is None:
274 self.__inbox = gmProviderInbox.cProviderInbox(provider_id = self._payload[self._idx['pk_staff']])
275 return self.__inbox
276
279
280 inbox = property(_get_inbox, _set_inbox)
281 #============================================================
284 #============================================================
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 """
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 #--------------------------------------------------------
326 #--------------------------------------------------------
327 # __getitem__ handling
328 #--------------------------------------------------------
330 """Return any attribute if known how to retrieve it by proxy.
331 """
332 return self.provider[aVar]
333 #--------------------------------------------------------
334 # __s/getattr__ handling
335 #--------------------------------------------------------
341 # raise AttributeError
342 #============================================================
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 #--------------------------------------------------------
367 ID = property(_get_ID, _set_ID)
368 #--------------------------------------------------------
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 #--------------------------------------------------------
391 #--------------------------------------------------------
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
408
409 is_patient = property(_get_is_patient, _set_is_patient)
410 #--------------------------------------------------------
411 # identity API
412 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
747 """Link an occupation with a patient, creating the occupation if it does not exists.
748
749 @param occupation The name of the occupation to link the patient to.
750 """
751 if (activities is None) and (occupation is None):
752 return True
753
754 occupation = occupation.strip()
755 if len(occupation) == 0:
756 return True
757
758 if activities is not None:
759 activities = activities.strip()
760
761 args = {'act': activities, 'pat_id': self.pk_obj, 'job': occupation}
762
763 cmd = u"select activities from dem.v_person_jobs where pk_identity = %(pat_id)s and l10n_occupation = _(%(job)s)"
764 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
765
766 queries = []
767 if len(rows) == 0:
768 queries.append ({
769 'cmd': u"INSERT INTO dem.lnk_job2person (fk_identity, fk_occupation, activities) VALUES (%(pat_id)s, dem.create_occupation(%(job)s), %(act)s)",
770 'args': args
771 })
772 else:
773 if rows[0]['activities'] != activities:
774 queries.append ({
775 'cmd': u"update dem.lnk_job2person set activities=%(act)s where fk_identity=%(pat_id)s and fk_occupation=(select id from dem.occupation where _(name) = _(%(job)s))",
776 'args': args
777 })
778
779 rows, idx = gmPG2.run_rw_queries(queries = queries)
780
781 return True
782 #--------------------------------------------------------
784 if occupation is None:
785 return True
786 occupation = occupation.strip()
787 cmd = u"delete from dem.lnk_job2person where fk_identity=%(pk)s and fk_occupation in (select id from dem.occupation where _(name) = _(%(job)s))"
788 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj, 'job': occupation}}])
789 return True
790 #--------------------------------------------------------
791 # comms API
792 #--------------------------------------------------------
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 #--------------------------------------------------------
812 - def link_comm_channel(self, comm_medium=None, url=None, is_confidential=False, pk_channel_type=None):
813 """Link a communication medium with a patient.
814
815 @param comm_medium The name of the communication medium.
816 @param url The communication resource locator.
817 @type url A types.StringType instance.
818 @param is_confidential Wether the data must be treated as confidential.
819 @type is_confidential A types.BooleanType instance.
820 """
821 comm_channel = gmDemographicRecord.create_comm_channel (
822 comm_medium = comm_medium,
823 url = url,
824 is_confidential = is_confidential,
825 pk_channel_type = pk_channel_type,
826 pk_identity = self.pk_obj
827 )
828 return comm_channel
829 #--------------------------------------------------------
831 gmDemographicRecord.delete_comm_channel (
832 pk = comm_channel['pk_lnk_identity2comm'],
833 pk_patient = self.pk_obj
834 )
835 #--------------------------------------------------------
836 # contacts API
837 #--------------------------------------------------------
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 #--------------------------------------------------------
855 - def link_address(self, number=None, street=None, postcode=None, urb=None, state=None, country=None, subunit=None, suburb=None, id_type=None):
856 """Link an address with a patient, creating the address if it does not exists.
857
858 @param number The number of the address.
859 @param street The name of the street.
860 @param postcode The postal code of the address.
861 @param urb The name of town/city/etc.
862 @param state The code of the state.
863 @param country The code of the country.
864 @param id_type The primary key of the address type.
865 """
866 # create/get address
867 adr = gmDemographicRecord.create_address (
868 country = country,
869 state = state,
870 urb = urb,
871 suburb = suburb,
872 postcode = postcode,
873 street = street,
874 number = number,
875 subunit = subunit
876 )
877
878 # already linked ?
879 cmd = u"select * from dem.lnk_person_org_address where id_identity = %s and id_address = %s"
880 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj, adr['pk_address']]}])
881 # no, link to person
882 if len(rows) == 0:
883 args = {'id': self.pk_obj, 'adr': adr['pk_address'], 'type': id_type}
884 if id_type is None:
885 cmd = u"""
886 insert into dem.lnk_person_org_address(id_identity, id_address)
887 values (%(id)s, %(adr)s)"""
888 else:
889 cmd = u"""
890 insert into dem.lnk_person_org_address(id_identity, id_address, id_type)
891 values (%(id)s, %(adr)s, %(type)s)"""
892 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
893 else:
894 # already linked - but needs to change type ?
895 if id_type is not None:
896 r = rows[0]
897 if r['id_type'] != id_type:
898 cmd = "update dem.lnk_person_org_address set id_type = %(type)s where id = %(id)s"
899 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'type': id_type, 'id': r['id']}}])
900
901 return adr
902 #----------------------------------------------------------------------
904 """Remove an address from the patient.
905
906 The address itself stays in the database.
907 The address can be either cAdress or cPatientAdress.
908 """
909 cmd = u"delete from dem.lnk_person_org_address where id_identity = %(person)s and id_address = %(adr)s"
910 args = {'person': self.pk_obj, 'adr': address['pk_address']}
911 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
912 #----------------------------------------------------------------------
913 # relatives API
914 #----------------------------------------------------------------------
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 #--------------------------------------------------------
950 # create new relative
951 id_new_relative = create_dummy_identity()
952
953 relative = cIdentity(aPK_obj=id_new_relative)
954 # pre-fill with data from ourselves
955 # relative.copy_addresses(self)
956 relative.add_name( '**?**', self.get_names()['lastnames'])
957 # and link the two
958 if self._ext_cache.has_key('relatives'):
959 del self._ext_cache['relatives']
960 cmd = u"""
961 insert into dem.lnk_person2relative (
962 id_identity, id_relative, id_relation_type
963 ) values (
964 %s, %s, (select id from dem.relation_types where description = %s)
965 )"""
966 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [self.ID, id_new_relative, rel_type ]}])
967 return True
968 #----------------------------------------------------------------------
972 #----------------------------------------------------------------------
973 # age/dob related
974 #----------------------------------------------------------------------
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 #----------------------------------------------------------------------
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 #----------------------------------------------------------------------
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 #--------------------------------------------------------
1013 return gmProviderInbox.get_inbox_messages(pk_patient = self._payload[self._idx['pk_identity']])
1014
1017
1018 messages = property(_get_messages, _set_messages)
1019 #--------------------------------------------------------
1022 #----------------------------------------------------------------------
1023 # convenience
1024 #----------------------------------------------------------------------
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 #============================================================
1035 """Represents a staff member which is a person.
1036
1037 - a specializing subclass of cIdentity turning it into a staff member
1038 """
1042 #--------------------------------------------------------
1045 #============================================================
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 """
1053 cIdentity.__init__(self, aPK_obj=aPK_obj, row=row)
1054 self.__db_cache = {}
1055 self.__emr_access_lock = threading.Lock()
1056 #--------------------------------------------------------
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 #----------------------------------------------------------
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 #--------------------------------------------------------
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 #============================================================
1092 """Patient Borg to hold currently active patient.
1093
1094 There may be many instances of this but they all share state.
1095 """
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 #--------------------------------------------------------
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 #--------------------------------------------------------
1169 #--------------------------------------------------------
1170 # external API
1171 #--------------------------------------------------------
1173 if not callable(callback):
1174 raise TypeError(u'callback [%s] not callable' % callback)
1175
1176 self.__pre_selection_callbacks.append(callback)
1177 #--------------------------------------------------------
1180
1183
1184 connected = property(_get_connected, _set_connected)
1185 #--------------------------------------------------------
1188
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
1262 """Return any attribute if known how to retrieve it by proxy.
1263 """
1264 return self.patient[attribute]
1265 #--------------------------------------------------------
1268 #============================================================
1270 """UI independant i18n aware patient searcher."""
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 #--------------------------------------------------------
1278 try:
1279 self.curs.close()
1280 except: pass
1281 try:
1282 self.conn.close()
1283 except: pass
1284 #--------------------------------------------------------
1285 # public API
1286 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #============================================================
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 #============================================================
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 #============================================================
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 #============================================================
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 #============================================================
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #============================================================
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 #============================================================
2110 return [ cIdentity(aPK_obj = pk) for pk in pks ]
2111 #============================================================
2113 from Gnumed.business import gmXdtObjects
2114 return gmXdtObjects.read_person_from_xdt(filename=filename, encoding=encoding, dob_format=dob_format)
2115 #============================================================
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
2187
2188 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Feb 9 04:01:39 2010 | http://epydoc.sourceforge.net |