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