1
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
13 import sys, os.path, time, re as regex, string, types, datetime as pyDT, codecs, threading, logging
14
15
16
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
36
37
38
39
41 return 'firstnames lastnames dob gender'.split()
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
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
157 return getattr(self, attr)
158
159 -class cPersonName(gmBusinessDBObject.cBusinessDBObject):
160 _cmd_fetch_payload = u"select * from dem.v_person_names where pk_name = %s"
161 _cmds_store_payload = [
162 u"""update dem.names set
163 active = False
164 where
165 %(active_name)s is True and -- act only when needed and only
166 id_identity = %(pk_identity)s and -- on names of this identity
167 active is True and -- which are active
168 id != %(pk_name)s -- but NOT *this* name
169 """,
170 u"""update dem.names set
171 active = %(active_name)s,
172 preferred = %(preferred)s,
173 comment = %(comment)s
174 where
175 id = %(pk_name)s and
176 id_identity = %(pk_identity)s and -- belt and suspenders
177 xmin = %(xmin_name)s""",
178 u"""select xmin as xmin_name from dem.names where id = %(pk_name)s"""
179 ]
180 _updatable_fields = ['active_name', 'preferred', 'comment']
181
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
204 -class cStaff(gmBusinessDBObject.cBusinessDBObject):
205 _cmd_fetch_payload = u"select * from dem.v_staff where pk_staff=%s"
206 _cmds_store_payload = [
207 u"""update dem.staff set
208 fk_role = %(pk_role)s,
209 short_alias = %(short_alias)s,
210 comment = gm.nullify_empty_string(%(comment)s),
211 is_active = %(is_active)s,
212 db_user = %(db_user)s
213 where
214 pk=%(pk_staff)s and
215 xmin = %(xmin_staff)s""",
216 u"""select xmin_staff from dem.v_staff where pk_identity=%(pk_identity)s"""
217 ]
218 _updatable_fields = ['pk_role', 'short_alias', 'comment', 'is_active', 'db_user']
219
220 - def __init__(self, aPK_obj=None, row=None):
221
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
242 self.__is_current_user = (gmPG2.get_current_user() == self._payload[self._idx['db_user']])
243
244 self.__inbox = None
245
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
297 try:
298 self.provider
299 except AttributeError:
300 self.provider = gmNull.cNull()
301
302
303 if provider is None:
304 return None
305
306
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
311 if self.provider['pk_staff'] == provider['pk_staff']:
312 return None
313
314
315 if isinstance(self.provider, gmNull.cNull):
316 self.provider = provider
317 return None
318
319
320 raise ValueError, 'provider change [%s] -> [%s] not yet supported' % (self.provider['pk_staff'], provider['pk_staff'])
321
322
325
326
327
329 """Return any attribute if known how to retrieve it by proxy.
330 """
331 return self.provider[aVar]
332
333
334
336 if attribute == 'provider':
337 raise AttributeError
338 if not isinstance(self.provider, gmNull.cNull):
339 return getattr(self.provider, attribute)
340
341
342 -class cIdentity(gmBusinessDBObject.cBusinessDBObject):
343 _cmd_fetch_payload = u"select * from dem.v_basic_person where pk_identity = %s"
344 _cmds_store_payload = [
345 u"""update dem.identity set
346 gender = %(gender)s,
347 dob = %(dob)s,
348 tob = %(tob)s,
349 cob = gm.nullify_empty_string(%(cob)s),
350 title = gm.nullify_empty_string(%(title)s),
351 fk_marital_status = %(pk_marital_status)s,
352 karyotype = gm.nullify_empty_string(%(karyotype)s),
353 pupic = gm.nullify_empty_string(%(pupic)s),
354 deceased = %(deceased)s,
355 emergency_contact = gm.nullify_empty_string(%(emergency_contact)s),
356 fk_emergency_contact = %(pk_emergency_contact)s
357 where
358 pk = %(pk_identity)s and
359 xmin = %(xmin_identity)s""",
360 u"""select xmin_identity from dem.v_basic_person where pk_identity = %(pk_identity)s"""
361 ]
362 _updatable_fields = [
363 "title",
364 "dob",
365 "tob",
366 "cob",
367 "gender",
368 "pk_marital_status",
369 "karyotype",
370 "pupic",
371 'deceased',
372 'emergency_contact',
373 'pk_emergency_contact'
374 ]
375
377 return self._payload[self._idx['pk_identity']]
379 raise AttributeError('setting ID of identity is not allowed')
380 ID = property(_get_ID, _set_ID)
381
383
384 if attribute == 'dob':
385 if value is not None:
386
387 if isinstance(value, pyDT.datetime):
388 if value.tzinfo is None:
389 raise ValueError('datetime.datetime instance is lacking a time zone: [%s]' % dt.isoformat())
390 else:
391 raise TypeError, '[%s]: type [%s] (%s) invalid for attribute [dob], must be datetime.datetime or None' % (self.__class__.__name__, type(value), value)
392
393
394 if self._payload[self._idx['dob']] is not None:
395 old_dob = self._payload[self._idx['dob']].strftime('%Y %m %d %H %M %S')
396 new_dob = value.strftime('%Y %m %d %H %M %S')
397 if new_dob == old_dob:
398 return
399
400 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
401
404
406 cmd = u"""
407 select exists (
408 select 1
409 from clin.v_emr_journal
410 where
411 pk_patient = %(pat)s
412 and
413 soap_cat is not null
414 )"""
415 args = {'pat': self._payload[self._idx['pk_identity']]}
416 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
417 return rows[0][0]
418
420 raise AttributeError('setting is_patient status of identity is not allowed')
421
422 is_patient = property(_get_is_patient, _set_is_patient)
423
424
425
427 for name in self.get_names():
428 if name['active_name'] is True:
429 return name
430
431 _log.error('cannot retrieve active name for patient [%s]' % self._payload[self._idx['pk_identity']])
432 return None
433
435 cmd = u"select * from dem.v_person_names where pk_identity = %(pk_pat)s"
436 rows, idx = gmPG2.run_ro_queries (
437 queries = [{
438 'cmd': cmd,
439 'args': {'pk_pat': self._payload[self._idx['pk_identity']]}
440 }],
441 get_col_idx = True
442 )
443
444 if len(rows) == 0:
445
446 return []
447
448 names = [ cPersonName(row = {'idx': idx, 'data': r, 'pk_field': 'pk_name'}) for r in rows ]
449 return names
450
459
461 return '%(sex)s%(title)s %(last)s, %(first)s%(nick)s' % {
462 'last': self._payload[self._idx['lastnames']],
463 'first': self._payload[self._idx['firstnames']],
464 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u' (%s)', u'%s'),
465 'sex': map_gender2salutation(self._payload[self._idx['gender']]),
466 'title': gmTools.coalesce(self._payload[self._idx['title']], u'', u' %s', u'%s')
467 }
468
470 return '%(last)s,%(title)s %(first)s%(nick)s' % {
471 'last': self._payload[self._idx['lastnames']],
472 'title': gmTools.coalesce(self._payload[self._idx['title']], u'', u' %s', u'%s'),
473 'first': self._payload[self._idx['firstnames']],
474 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u' (%s)', u'%s')
475 }
476
477 - def add_name(self, firstnames, lastnames, active=True):
478 """Add a name.
479
480 @param firstnames The first names.
481 @param lastnames The last names.
482 @param active When True, the new name will become the active one (hence setting other names to inactive)
483 @type active A types.BooleanType instance
484 """
485 name = create_name(self.ID, firstnames, lastnames, active)
486 if active:
487 self.refetch_payload()
488 return name
489
491 cmd = u"delete from dem.names where id = %(name)s and id_identity = %(pat)s"
492 args = {'name': name['pk_name'], 'pat': self.ID}
493 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
494
495
496
497
499 """
500 Set the nickname. Setting the nickname only makes sense for the currently
501 active name.
502 @param nickname The preferred/nick/warrior name to set.
503 """
504 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': u"select dem.set_nickname(%s, %s)", 'args': [self.ID, nickname]}])
505 self.refetch_payload()
506 return True
507
508
509
510
511
512
513
514
515
516
517 - def add_external_id(self, type_name=None, value=None, issuer=None, comment=None, context=u'p', pk_type=None):
518 """Adds an external ID to the patient.
519
520 creates ID type if necessary
521 context hardcoded to 'p' for now
522 """
523
524
525 if pk_type is not None:
526 cmd = u"""
527 select * from dem.v_external_ids4identity where
528 pk_identity = %(pat)s and
529 pk_type = %(pk_type)s and
530 value = %(val)s"""
531 else:
532
533 if issuer is None:
534 cmd = u"""
535 select * from dem.v_external_ids4identity where
536 pk_identity = %(pat)s and
537 name = %(name)s and
538 value = %(val)s"""
539 else:
540 cmd = u"""
541 select * from dem.v_external_ids4identity where
542 pk_identity = %(pat)s and
543 name = %(name)s and
544 value = %(val)s and
545 issuer = %(issuer)s"""
546 args = {
547 'pat': self.ID,
548 'name': type_name,
549 'val': value,
550 'issuer': issuer,
551 'pk_type': pk_type
552 }
553 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
554
555
556 if len(rows) == 0:
557
558 args = {
559 'pat': self.ID,
560 'val': value,
561 'type_name': type_name,
562 'pk_type': pk_type,
563 'issuer': issuer,
564 'ctxt': context,
565 'comment': comment
566 }
567
568 if pk_type is None:
569 cmd = u"""insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values (
570 %(val)s,
571 (select dem.add_external_id_type(%(type_name)s, %(issuer)s, %(ctxt)s)),
572 %(comment)s,
573 %(pat)s
574 )"""
575 else:
576 cmd = u"""insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values (
577 %(val)s,
578 %(pk_type)s,
579 %(comment)s,
580 %(pat)s
581 )"""
582
583 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
584
585
586 else:
587 row = rows[0]
588 if comment is not None:
589
590 if gmTools.coalesce(row['comment'], '').find(comment.strip()) == -1:
591 comment = '%s%s' % (gmTools.coalesce(row['comment'], '', '%s // '), comment.strip)
592 cmd = u"update dem.lnk_identity2ext_id set comment = %(comment)s where id=%(pk)s"
593 args = {'comment': comment, 'pk': row['pk_id']}
594 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
595
596 - def update_external_id(self, pk_id=None, type=None, value=None, issuer=None, comment=None):
597 """Edits an existing external ID.
598
599 creates ID type if necessary
600 context hardcoded to 'p' for now
601 """
602 cmd = u"""
603 update dem.lnk_identity2ext_id set
604 fk_origin = (select dem.add_external_id_type(%(type)s, %(issuer)s, %(ctxt)s)),
605 external_id = %(value)s,
606 comment = %(comment)s
607 where id = %(pk)s"""
608 args = {'pk': pk_id, 'ctxt': u'p', 'value': value, 'type': type, 'issuer': issuer, 'comment': comment}
609 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
610
612 where_parts = ['pk_identity = %(pat)s']
613 args = {'pat': self.ID}
614
615 if id_type is not None:
616 where_parts.append(u'name = %(name)s')
617 args['name'] = id_type.strip()
618
619 if issuer is not None:
620 where_parts.append(u'issuer = %(issuer)s')
621 args['issuer'] = issuer.strip()
622
623 if context is not None:
624 where_parts.append(u'context = %(ctxt)s')
625 args['ctxt'] = context.strip()
626
627 cmd = u"select * from dem.v_external_ids4identity where %s" % ' and '.join(where_parts)
628 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
629
630 return rows
631
633 cmd = u"""
634 delete from dem.lnk_identity2ext_id
635 where id_identity = %(pat)s and id = %(pk)s"""
636 args = {'pat': self.ID, 'pk': pk_ext_id}
637 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
638
640 """Merge another identity into this one.
641
642 Keep this one. Delete other one."""
643
644 if other_identity.ID == self.ID:
645 return True, None
646
647 curr_pat = gmCurrentPatient()
648 if curr_pat.connected:
649 if other_identity.ID == curr_pat.ID:
650 return False, _('Cannot merge active patient into another patient.')
651
652 queries = []
653 args = {'old_pat': other_identity.ID, 'new_pat': self.ID}
654
655
656 queries.append ({
657 'cmd': u'delete from clin.allergy_state where pk = (select pk_allergy_state from clin.v_pat_allergy_state where pk_patient = %(old_pat)s)',
658 'args': args
659 })
660
661
662
663 queries.append ({
664 'cmd': u'update dem.names set active = False where id_identity = %(old_pat)s',
665 'args': args
666 })
667
668
669 FKs = gmPG2.get_foreign_keys2column (
670 schema = u'dem',
671 table = u'identity',
672 column = u'pk'
673 )
674
675
676 cmd_template = u'update %s set %s = %%(new_pat)s where %s = %%(old_pat)s'
677 for FK in FKs:
678 queries.append ({
679 'cmd': cmd_template % (FK['referencing_table'], FK['referencing_column'], FK['referencing_column']),
680 'args': args
681 })
682
683
684 queries.append ({
685 'cmd': u'delete from dem.identity where pk = %(old_pat)s',
686 'args': args
687 })
688
689 _log.warning('identity [%s] is about to assimilate identity [%s]', self.ID, other_identity.ID)
690
691 gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, end_tx = True)
692
693 self.add_external_id (
694 type_name = u'merged GNUmed identity primary key',
695 value = u'GNUmed::pk::%s' % other_identity.ID,
696 issuer = u'GNUmed'
697 )
698
699 return True, None
700
701
703 cmd = u"""
704 insert into clin.waiting_list (fk_patient, urgency, comment, area, list_position)
705 values (
706 %(pat)s,
707 %(urg)s,
708 %(cmt)s,
709 %(area)s,
710 (select coalesce((max(list_position) + 1), 1) from clin.waiting_list)
711 )"""
712 args = {'pat': self.ID, 'urg': urgency, 'cmt': comment, 'area': zone}
713 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], verbose=True)
714
715 - def export_as_gdt(self, filename=None, encoding='iso-8859-15', external_id_type=None):
716
717 template = u'%s%s%s\r\n'
718
719 file = codecs.open (
720 filename = filename,
721 mode = 'wb',
722 encoding = encoding,
723 errors = 'strict'
724 )
725
726 file.write(template % (u'013', u'8000', u'6301'))
727 file.write(template % (u'013', u'9218', u'2.10'))
728 if external_id_type is None:
729 file.write(template % (u'%03d' % (9 + len(str(self.ID))), u'3000', self.ID))
730 else:
731 ext_ids = self.get_external_ids(id_type = external_id_type)
732 if len(ext_ids) > 0:
733 file.write(template % (u'%03d' % (9 + len(ext_ids[0]['value'])), u'3000', ext_ids[0]['value']))
734 file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['lastnames']])), u'3101', self._payload[self._idx['lastnames']]))
735 file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['firstnames']])), u'3102', self._payload[self._idx['firstnames']]))
736 file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['dob']].strftime('%d%m%Y'))), u'3103', self._payload[self._idx['dob']].strftime('%d%m%Y')))
737 file.write(template % (u'010', u'3110', gmXdtMappings.map_gender_gm2xdt[self._payload[self._idx['gender']]]))
738 file.write(template % (u'025', u'6330', 'GNUmed::9206::encoding'))
739 file.write(template % (u'%03d' % (9 + len(encoding)), u'6331', encoding))
740 if external_id_type is None:
741 file.write(template % (u'029', u'6332', u'GNUmed::3000::source'))
742 file.write(template % (u'017', u'6333', u'internal'))
743 else:
744 if len(ext_ids) > 0:
745 file.write(template % (u'029', u'6332', u'GNUmed::3000::source'))
746 file.write(template % (u'%03d' % (9 + len(external_id_type)), u'6333', external_id_type))
747
748 file.close()
749
750
751
753 cmd = u"select * from dem.v_person_jobs where pk_identity=%s"
754 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
755 return rows
756
758 """Link an occupation with a patient, creating the occupation if it does not exists.
759
760 @param occupation The name of the occupation to link the patient to.
761 """
762 if (activities is None) and (occupation is None):
763 return True
764
765 occupation = occupation.strip()
766 if len(occupation) == 0:
767 return True
768
769 if activities is not None:
770 activities = activities.strip()
771
772 args = {'act': activities, 'pat_id': self.pk_obj, 'job': occupation}
773
774 cmd = u"select activities from dem.v_person_jobs where pk_identity = %(pat_id)s and l10n_occupation = _(%(job)s)"
775 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
776
777 queries = []
778 if len(rows) == 0:
779 queries.append ({
780 'cmd': u"INSERT INTO dem.lnk_job2person (fk_identity, fk_occupation, activities) VALUES (%(pat_id)s, dem.create_occupation(%(job)s), %(act)s)",
781 'args': args
782 })
783 else:
784 if rows[0]['activities'] != activities:
785 queries.append ({
786 '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))",
787 'args': args
788 })
789
790 rows, idx = gmPG2.run_rw_queries(queries = queries)
791
792 return True
793
795 if occupation is None:
796 return True
797 occupation = occupation.strip()
798 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))"
799 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj, 'job': occupation}}])
800 return True
801
802
803
805 cmd = u"select * from dem.v_person_comms where pk_identity = %s"
806 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx = True)
807
808 filtered = rows
809
810 if comm_medium is not None:
811 filtered = []
812 for row in rows:
813 if row['comm_type'] == comm_medium:
814 filtered.append(row)
815
816 return [ gmDemographicRecord.cCommChannel(row = {
817 'pk_field': 'pk_lnk_identity2comm',
818 'data': r,
819 'idx': idx
820 }) for r in filtered
821 ]
822
823 - def link_comm_channel(self, comm_medium=None, url=None, is_confidential=False, pk_channel_type=None):
824 """Link a communication medium with a patient.
825
826 @param comm_medium The name of the communication medium.
827 @param url The communication resource locator.
828 @type url A types.StringType instance.
829 @param is_confidential Wether the data must be treated as confidential.
830 @type is_confidential A types.BooleanType instance.
831 """
832 comm_channel = gmDemographicRecord.create_comm_channel (
833 comm_medium = comm_medium,
834 url = url,
835 is_confidential = is_confidential,
836 pk_channel_type = pk_channel_type,
837 pk_identity = self.pk_obj
838 )
839 return comm_channel
840
846
847
848
850 cmd = u"select * from dem.v_pat_addresses where pk_identity=%s"
851 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx=True)
852 addresses = []
853 for r in rows:
854 addresses.append(gmDemographicRecord.cPatientAddress(row={'idx': idx, 'data': r, 'pk_field': 'pk_address'}))
855
856 filtered = addresses
857
858 if address_type is not None:
859 filtered = []
860 for adr in addresses:
861 if adr['address_type'] == address_type:
862 filtered.append(adr)
863
864 return filtered
865
866 - def link_address(self, number=None, street=None, postcode=None, urb=None, state=None, country=None, subunit=None, suburb=None, id_type=None):
867 """Link an address with a patient, creating the address if it does not exists.
868
869 @param number The number of the address.
870 @param street The name of the street.
871 @param postcode The postal code of the address.
872 @param urb The name of town/city/etc.
873 @param state The code of the state.
874 @param country The code of the country.
875 @param id_type The primary key of the address type.
876 """
877
878 adr = gmDemographicRecord.create_address (
879 country = country,
880 state = state,
881 urb = urb,
882 suburb = suburb,
883 postcode = postcode,
884 street = street,
885 number = number,
886 subunit = subunit
887 )
888
889
890 cmd = u"select * from dem.lnk_person_org_address where id_identity = %s and id_address = %s"
891 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj, adr['pk_address']]}])
892
893 if len(rows) == 0:
894 args = {'id': self.pk_obj, 'adr': adr['pk_address'], 'type': id_type}
895 if id_type is None:
896 cmd = u"""
897 insert into dem.lnk_person_org_address(id_identity, id_address)
898 values (%(id)s, %(adr)s)"""
899 else:
900 cmd = u"""
901 insert into dem.lnk_person_org_address(id_identity, id_address, id_type)
902 values (%(id)s, %(adr)s, %(type)s)"""
903 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
904 else:
905
906 if id_type is not None:
907 r = rows[0]
908 if r['id_type'] != id_type:
909 cmd = "update dem.lnk_person_org_address set id_type = %(type)s where id = %(id)s"
910 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'type': id_type, 'id': r['id']}}])
911
912 return adr
913
915 """Remove an address from the patient.
916
917 The address itself stays in the database.
918 The address can be either cAdress or cPatientAdress.
919 """
920 cmd = u"delete from dem.lnk_person_org_address where id_identity = %(person)s and id_address = %(adr)s"
921 args = {'person': self.pk_obj, 'adr': address['pk_address']}
922 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
923
924
925
927 cmd = u"""
928 select
929 t.description,
930 vbp.pk_identity as id,
931 title,
932 firstnames,
933 lastnames,
934 dob,
935 cob,
936 gender,
937 karyotype,
938 pupic,
939 pk_marital_status,
940 marital_status,+
941 xmin_identity,
942 preferred
943 from
944 dem.v_basic_person vbp, dem.relation_types t, dem.lnk_person2relative l
945 where
946 (
947 l.id_identity = %(pk)s and
948 vbp.pk_identity = l.id_relative and
949 t.id = l.id_relation_type
950 ) or (
951 l.id_relative = %(pk)s and
952 vbp.pk_identity = l.id_identity and
953 t.inverse = l.id_relation_type
954 )"""
955 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
956 if len(rows) == 0:
957 return []
958 return [(row[0], cIdentity(row = {'data': row[1:], 'idx':idx, 'pk_field': 'pk'})) for row in rows]
959
961
962 id_new_relative = create_dummy_identity()
963
964 relative = cIdentity(aPK_obj=id_new_relative)
965
966
967 relative.add_name( '**?**', self.get_names()['lastnames'])
968
969 if self._ext_cache.has_key('relatives'):
970 del self._ext_cache['relatives']
971 cmd = u"""
972 insert into dem.lnk_person2relative (
973 id_identity, id_relative, id_relation_type
974 ) values (
975 %s, %s, (select id from dem.relation_types where description = %s)
976 )"""
977 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [self.ID, id_new_relative, rel_type ]}])
978 return True
979
981
982 self.set_relative(None, relation)
983
984
985
1002
1003 - def dob_in_range(self, min_distance=u'1 week', max_distance=u'1 week'):
1004 cmd = u'select dem.dob_is_in_range(%(dob)s, %(min)s, %(max)s)'
1005 rows, idx = gmPG2.run_ro_queries (
1006 queries = [{
1007 'cmd': cmd,
1008 'args': {'dob': self['dob'], 'min': min_distance, 'max': max_distance}
1009 }]
1010 )
1011 return rows[0][0]
1012
1013
1014
1016 cmd = u'select * from clin.v_most_recent_encounters where pk_patient=%s'
1017 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self._payload[self._idx['pk_identity']]]}])
1018 if len(rows) > 0:
1019 return rows[0]
1020 else:
1021 return None
1022
1025
1028
1029 messages = property(_get_messages, _set_messages)
1030
1033
1034
1035
1037 """Format patient demographics into patient specific path name fragment."""
1038 return '%s-%s%s-%s' % (
1039 self._payload[self._idx['lastnames']].replace(u' ', u'_'),
1040 self._payload[self._idx['firstnames']].replace(u' ', u'_'),
1041 gmTools.coalesce(self._payload[self._idx['preferred']], u'', template_initial = u'-(%s)'),
1042 self.get_formatted_dob(format = '%Y-%m-%d', encoding = gmI18N.get_encoding())
1043 )
1044
1046 """Represents a staff member which is a person.
1047
1048 - a specializing subclass of cIdentity turning it into a staff member
1049 """
1053
1056
1058 """Represents a person which is a patient.
1059
1060 - a specializing subclass of cIdentity turning it into a patient
1061 - its use is to cache subobjects like EMR and document folder
1062 """
1063 - def __init__(self, aPK_obj=None, row=None):
1064 cIdentity.__init__(self, aPK_obj=aPK_obj, row=row)
1065 self.__db_cache = {}
1066 self.__emr_access_lock = threading.Lock()
1067
1069 """Do cleanups before dying.
1070
1071 - note that this may be called in a thread
1072 """
1073 if self.__db_cache.has_key('clinical record'):
1074 self.__db_cache['clinical record'].cleanup()
1075 if self.__db_cache.has_key('document folder'):
1076 self.__db_cache['document folder'].cleanup()
1077 cIdentity.cleanup(self)
1078
1080 if not self.__emr_access_lock.acquire(False):
1081 raise AttributeError('cannot access EMR')
1082 try:
1083 emr = self.__db_cache['clinical record']
1084 self.__emr_access_lock.release()
1085 return emr
1086 except KeyError:
1087 pass
1088
1089 self.__db_cache['clinical record'] = gmClinicalRecord.cClinicalRecord(aPKey = self._payload[self._idx['pk_identity']])
1090 self.__emr_access_lock.release()
1091 return self.__db_cache['clinical record']
1092
1094 try:
1095 return self.__db_cache['document folder']
1096 except KeyError:
1097 pass
1098
1099 self.__db_cache['document folder'] = gmDocuments.cDocumentFolder(aPKey = self._payload[self._idx['pk_identity']])
1100 return self.__db_cache['document folder']
1101
1103 """Patient Borg to hold currently active patient.
1104
1105 There may be many instances of this but they all share state.
1106 """
1107 - def __init__(self, patient=None, forced_reload=False):
1108 """Change or get currently active patient.
1109
1110 patient:
1111 * None: get currently active patient
1112 * -1: unset currently active patient
1113 * cPatient instance: set active patient if possible
1114 """
1115
1116 try:
1117 tmp = self.patient
1118 except AttributeError:
1119 self.patient = gmNull.cNull()
1120 self.__register_interests()
1121
1122
1123
1124 self.__lock_depth = 0
1125
1126 self.__pre_selection_callbacks = []
1127
1128
1129 if patient is None:
1130 return None
1131
1132
1133 if self.locked:
1134 _log.error('patient [%s] is locked, cannot change to [%s]' % (self.patient['pk_identity'], patient))
1135 return None
1136
1137
1138 if patient == -1:
1139 _log.debug('explicitly unsetting current patient')
1140 if not self.__run_pre_selection_callbacks():
1141 _log.debug('not unsetting current patient')
1142 return None
1143 self.__send_pre_selection_notification()
1144 self.patient.cleanup()
1145 self.patient = gmNull.cNull()
1146 self.__send_selection_notification()
1147 return None
1148
1149
1150 if not isinstance(patient, cPatient):
1151 _log.error('cannot set active patient to [%s], must be either None, -1 or cPatient instance' % str(patient))
1152 raise TypeError, 'gmPerson.gmCurrentPatient.__init__(): <patient> must be None, -1 or cPatient instance but is: %s' % str(patient)
1153
1154
1155 if (self.patient['pk_identity'] == patient['pk_identity']) and not forced_reload:
1156 return None
1157
1158
1159 _log.debug('patient change [%s] -> [%s] requested', self.patient['pk_identity'], patient['pk_identity'])
1160
1161
1162 if not self.__run_pre_selection_callbacks():
1163 _log.debug('not changing current patient')
1164 return None
1165 self.__send_pre_selection_notification()
1166 self.patient.cleanup()
1167 self.patient = patient
1168 self.patient.get_emr()
1169 self.__send_selection_notification()
1170
1171 return None
1172
1176
1180
1181
1182
1184 if not callable(callback):
1185 raise TypeError(u'callback [%s] not callable' % callback)
1186
1187 self.__pre_selection_callbacks.append(callback)
1188
1191
1193 raise AttributeError(u'invalid to set <connected> state')
1194
1195 connected = property(_get_connected, _set_connected)
1196
1198 return (self.__lock_depth > 0)
1199
1201 if locked:
1202 self.__lock_depth = self.__lock_depth + 1
1203 gmDispatcher.send(signal='patient_locked')
1204 else:
1205 if self.__lock_depth == 0:
1206 _log.error('lock/unlock imbalance, trying to refcount lock depth below 0')
1207 return
1208 else:
1209 self.__lock_depth = self.__lock_depth - 1
1210 gmDispatcher.send(signal='patient_unlocked')
1211
1212 locked = property(_get_locked, _set_locked)
1213
1215 _log.info('forced patient unlock at lock depth [%s]' % self.__lock_depth)
1216 self.__lock_depth = 0
1217 gmDispatcher.send(signal='patient_unlocked')
1218
1219
1220
1222 if isinstance(self.patient, gmNull.cNull):
1223 return True
1224
1225 for call_back in self.__pre_selection_callbacks:
1226 try:
1227 successful = call_back()
1228 except:
1229 _log.exception('callback [%s] failed', call_back)
1230 print "*** pre-selection callback failed ***"
1231 print type(call_back)
1232 print call_back
1233 return False
1234
1235 if not successful:
1236 _log.debug('callback [%s] returned False', call_back)
1237 return False
1238
1239 return True
1240
1242 """Sends signal when another patient is about to become active.
1243
1244 This does NOT wait for signal handlers to complete.
1245 """
1246 kwargs = {
1247 'signal': u'pre_patient_selection',
1248 'sender': id(self.__class__),
1249 'pk_identity': self.patient['pk_identity']
1250 }
1251 gmDispatcher.send(**kwargs)
1252
1254 """Sends signal when another patient has actually been made active."""
1255 kwargs = {
1256 'signal': u'post_patient_selection',
1257 'sender': id(self.__class__),
1258 'pk_identity': self.patient['pk_identity']
1259 }
1260 gmDispatcher.send(**kwargs)
1261
1262
1263
1265 if attribute == 'patient':
1266 raise AttributeError
1267 if not isinstance(self.patient, gmNull.cNull):
1268 return getattr(self.patient, attribute)
1269
1270
1271
1273 """Return any attribute if known how to retrieve it by proxy.
1274 """
1275 return self.patient[attribute]
1276
1279
1281 """UI independant i18n aware patient searcher."""
1283 self._generate_queries = self._generate_queries_de
1284
1285 self.conn = gmPG2.get_connection()
1286 self.curs = self.conn.cursor()
1287
1289 try:
1290 self.curs.close()
1291 except: pass
1292 try:
1293 self.conn.close()
1294 except: pass
1295
1296
1297
1298 - def get_patients(self, search_term = None, a_locale = None, dto = None):
1299 identities = self.get_identities(search_term, a_locale, dto)
1300 if identities is None:
1301 return None
1302 return [cPatient(aPK_obj=ident['pk_identity']) for ident in identities]
1303
1304 - def get_identities(self, search_term = None, a_locale = None, dto = None):
1305 """Get patient identity objects for given parameters.
1306
1307 - either search term or search dict
1308 - dto contains structured data that doesn't need to be parsed (cDTO_person)
1309 - dto takes precedence over search_term
1310 """
1311 parse_search_term = (dto is None)
1312
1313 if not parse_search_term:
1314 queries = self._generate_queries_from_dto(dto)
1315 if queries is None:
1316 parse_search_term = True
1317 if len(queries) == 0:
1318 parse_search_term = True
1319
1320 if parse_search_term:
1321
1322 if a_locale is not None:
1323 print "temporary change of locale on patient search not implemented"
1324 _log.warning("temporary change of locale on patient search not implemented")
1325
1326 if search_term is None:
1327 raise ValueError('need search term (dto AND search_term are None)')
1328
1329 queries = self._generate_queries(search_term)
1330
1331
1332 if len(queries) == 0:
1333 _log.error('query tree empty')
1334 _log.error('[%s] [%s] [%s]' % (search_term, a_locale, str(dto)))
1335 return None
1336
1337
1338 identities = []
1339
1340 for query in queries:
1341 _log.debug("running %s" % query)
1342 try:
1343 rows, idx = gmPG2.run_ro_queries(queries = [query], get_col_idx=True)
1344 except:
1345 _log.exception('error running query')
1346 continue
1347 if len(rows) == 0:
1348 continue
1349 identities.extend (
1350 [ cIdentity(row = {'pk_field': 'pk_identity', 'data': row, 'idx': idx}) for row in rows ]
1351 )
1352
1353 pks = []
1354 unique_identities = []
1355 for identity in identities:
1356 if identity['pk_identity'] in pks:
1357 continue
1358 pks.append(identity['pk_identity'])
1359 unique_identities.append(identity)
1360
1361 return unique_identities
1362
1363
1364
1366 """Transform some characters into a regex."""
1367 if aString.strip() == u'':
1368 return aString
1369
1370
1371 normalized = aString.replace(u'Ä', u'(Ä|AE|Ae|A|E)')
1372 normalized = normalized.replace(u'Ö', u'(Ö|OE|Oe|O)')
1373 normalized = normalized.replace(u'Ü', u'(Ü|UE|Ue|U)')
1374 normalized = normalized.replace(u'ä', u'(ä|ae|e|a)')
1375 normalized = normalized.replace(u'ö', u'(ö|oe|o)')
1376 normalized = normalized.replace(u'ü', u'(ü|ue|u|y)')
1377 normalized = normalized.replace(u'ß', u'(ß|sz|ss|s)')
1378
1379
1380
1381 normalized = normalized.replace(u'é', u'***DUMMY***')
1382 normalized = normalized.replace(u'è', u'***DUMMY***')
1383 normalized = normalized.replace(u'***DUMMY***', u'(é|e|è|ä|ae)')
1384
1385
1386 normalized = normalized.replace(u'v', u'***DUMMY***')
1387 normalized = normalized.replace(u'f', u'***DUMMY***')
1388 normalized = normalized.replace(u'ph', u'***DUMMY***')
1389 normalized = normalized.replace(u'***DUMMY***', u'(v|f|ph)')
1390
1391
1392 normalized = normalized.replace(u'Th',u'***DUMMY***')
1393 normalized = normalized.replace(u'T', u'***DUMMY***')
1394 normalized = normalized.replace(u'***DUMMY***', u'(Th|T)')
1395 normalized = normalized.replace(u'th', u'***DUMMY***')
1396 normalized = normalized.replace(u't', u'***DUMMY***')
1397 normalized = normalized.replace(u'***DUMMY***', u'(th|t)')
1398
1399
1400 normalized = normalized.replace(u'"', u'***DUMMY***')
1401 normalized = normalized.replace(u"'", u'***DUMMY***')
1402 normalized = normalized.replace(u'`', u'***DUMMY***')
1403 normalized = normalized.replace(u'***DUMMY***', u"""("|'|`|***DUMMY***|\s)*""")
1404 normalized = normalized.replace(u'-', u"""(-|\s)*""")
1405 normalized = normalized.replace(u'|***DUMMY***|', u'|-|')
1406
1407 if aggressive:
1408 pass
1409
1410
1411 _log.debug('[%s] -> [%s]' % (aString, normalized))
1412
1413 return normalized
1414
1415
1416
1417
1418
1419
1420
1422 """Compose queries if search term seems unambigous."""
1423 queries = []
1424
1425
1426 if regex.match(u"^(\s|\t)*\d+(\s|\t)*$", raw, flags = regex.LOCALE | regex.UNICODE):
1427 _log.debug("[%s]: a PK or DOB" % raw)
1428 tmp = raw.strip()
1429 queries.append ({
1430 'cmd': u"select *, %s::text as match_type FROM dem.v_basic_person WHERE pk_identity = %s order by lastnames, firstnames, dob",
1431 'args': [_('internal patient ID'), tmp]
1432 })
1433 queries.append ({
1434 'cmd': u"SELECT *, %s::text as match_type FROM dem.v_basic_person WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) order by lastnames, firstnames, dob",
1435 'args': [_('date of birth'), tmp.replace(',', '.')]
1436 })
1437 queries.append ({
1438 'cmd': u"""
1439 select vba.*, %s::text as match_type from dem.lnk_identity2ext_id li2ext_id, dem.v_basic_person vba
1440 where vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s)
1441 order by lastnames, firstnames, dob""",
1442 'args': [_('external patient ID'), tmp]
1443 })
1444 return queries
1445
1446
1447 if regex.match(u"^(\d|\s|\t)+$", raw, flags = regex.LOCALE | regex.UNICODE):
1448 _log.debug("[%s]: a DOB or PK" % raw)
1449 queries.append ({
1450 'cmd': u"SELECT *, %s::text as match_type FROM dem.v_basic_person WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) order by lastnames, firstnames, dob",
1451 'args': [_('date of birth'), raw.replace(',', '.')]
1452 })
1453 tmp = raw.replace(u' ', u'')
1454 tmp = tmp.replace(u'\t', u'')
1455 queries.append ({
1456 'cmd': u"SELECT *, %s::text as match_type from dem.v_basic_person WHERE pk_identity LIKE %s%%",
1457 'args': [_('internal patient ID'), tmp]
1458 })
1459 return queries
1460
1461
1462 if regex.match(u"^(\s|\t)*#(\d|\s|\t)+$", raw, flags = regex.LOCALE | regex.UNICODE):
1463 _log.debug("[%s]: a PK or external ID" % raw)
1464 tmp = raw.replace(u'#', u'')
1465 tmp = tmp.strip()
1466 tmp = tmp.replace(u' ', u'')
1467 tmp = tmp.replace(u'\t', u'')
1468
1469 queries.append ({
1470 'cmd': u"SELECT *, %s::text as match_type from dem.v_basic_person WHERE pk_identity = %s order by lastnames, firstnames, dob",
1471 'args': [_('internal patient ID'), tmp]
1472 })
1473
1474 tmp = raw.replace(u'#', u'')
1475 tmp = tmp.strip()
1476 tmp = tmp.replace(u' ', u'***DUMMY***')
1477 tmp = tmp.replace(u'\t', u'***DUMMY***')
1478 tmp = tmp.replace(u'***DUMMY***', u'(\s|\t|-|/)*')
1479 queries.append ({
1480 'cmd': u"""
1481 select vba.*, %s::text as match_type from dem.lnk_identity2ext_id li2ext_id, dem.v_basic_person vba
1482 where vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s)
1483 order by lastnames, firstnames, dob""",
1484 'args': [_('external patient ID'), tmp]
1485 })
1486 return queries
1487
1488
1489 if regex.match(u"^(\s|\t)*#.+$", raw, flags = regex.LOCALE | regex.UNICODE):
1490 _log.debug("[%s]: an external ID" % raw)
1491 tmp = raw.replace(u'#', u'')
1492 tmp = tmp.strip()
1493 tmp = tmp.replace(u' ', u'***DUMMY***')
1494 tmp = tmp.replace(u'\t', u'***DUMMY***')
1495 tmp = tmp.replace(u'-', u'***DUMMY***')
1496 tmp = tmp.replace(u'/', u'***DUMMY***')
1497 tmp = tmp.replace(u'***DUMMY***', u'(\s|\t|-|/)*')
1498 queries.append ({
1499 'cmd': u"""
1500 select vba.*, %s::text as match_type from dem.lnk_identity2ext_id li2ext_id, dem.v_basic_person vba
1501 where vba.pk_identity = li2ext_id.id_identity and lower(li2ext_id.external_id) ~* lower(%s)
1502 order by lastnames, firstnames, dob""",
1503 'args': [_('external patient ID'), tmp]
1504 })
1505 return queries
1506
1507
1508 if regex.match(u"^(\s|\t)*\d+(\s|\t|\.|\-|/)*\d+(\s|\t|\.|\-|/)*\d+(\s|\t|\.)*$", raw, flags = regex.LOCALE | regex.UNICODE):
1509 _log.debug("[%s]: a DOB" % raw)
1510 tmp = raw.strip()
1511 while u'\t\t' in tmp: tmp = tmp.replace(u'\t\t', u' ')
1512 while u' ' in tmp: tmp = tmp.replace(u' ', u' ')
1513
1514
1515
1516 queries.append ({
1517 'cmd': u"SELECT *, %s as match_type from dem.v_basic_person WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) order by lastnames, firstnames, dob",
1518 'args': [_('date of birth'), tmp.replace(',', '.')]
1519 })
1520 return queries
1521
1522
1523 if regex.match(u"^(\s|\t)*,(\s|\t)*([^0-9])+(\s|\t)*$", raw, flags = regex.LOCALE | regex.UNICODE):
1524 _log.debug("[%s]: a firstname" % raw)
1525 tmp = self._normalize_soundalikes(raw[1:].strip())
1526 cmd = u"""
1527 SELECT DISTINCT ON (pk_identity) * from (
1528 select *, %s as match_type from ((
1529 select vbp.*
1530 FROM dem.names, dem.v_basic_person vbp
1531 WHERE dem.names.firstnames ~ %s and vbp.pk_identity = dem.names.id_identity
1532 ) union all (
1533 select vbp.*
1534 FROM dem.names, dem.v_basic_person vbp
1535 WHERE dem.names.firstnames ~ %s and vbp.pk_identity = dem.names.id_identity
1536 )) as super_list order by lastnames, firstnames, dob
1537 ) as sorted_list"""
1538 queries.append ({
1539 'cmd': cmd,
1540 'args': [_('first name'), '^' + gmTools.capitalize(tmp, mode=gmTools.CAPS_NAMES), '^' + tmp]
1541 })
1542 return queries
1543
1544
1545 if regex.match(u"^(\s|\t)*(\*|\$).+$", raw, flags = regex.LOCALE | regex.UNICODE):
1546 _log.debug("[%s]: a DOB" % raw)
1547 tmp = raw.replace(u'*', u'')
1548 tmp = tmp.replace(u'$', u'')
1549 queries.append ({
1550 'cmd': u"SELECT *, %s as match_type from dem.v_basic_person WHERE dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) order by lastnames, firstnames, dob",
1551 'args': [_('date of birth'), tmp.replace(u',', u'.')]
1552 })
1553 return queries
1554
1555 return queries
1556
1557
1558
1560 """Generate generic queries.
1561
1562 - not locale dependant
1563 - data -> firstnames, lastnames, dob, gender
1564 """
1565 _log.debug(u'_generate_queries_from_dto("%s")' % dto)
1566
1567 if not isinstance(dto, cDTO_person):
1568 return None
1569
1570 vals = [_('name, gender, date of birth')]
1571 where_snippets = []
1572
1573 vals.append(dto.firstnames)
1574 where_snippets.append(u'firstnames=%s')
1575 vals.append(dto.lastnames)
1576 where_snippets.append(u'lastnames=%s')
1577
1578 if dto.dob is not None:
1579 vals.append(dto.dob)
1580
1581 where_snippets.append(u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s)")
1582
1583 if dto.gender is not None:
1584 vals.append(dto.gender)
1585 where_snippets.append('gender=%s')
1586
1587
1588 if len(where_snippets) == 0:
1589 _log.error('invalid search dict structure')
1590 _log.debug(data)
1591 return None
1592
1593 cmd = u"""
1594 select *, %%s as match_type from dem.v_basic_person
1595 where pk_identity in (
1596 select id_identity from dem.names where %s
1597 ) order by lastnames, firstnames, dob""" % ' and '.join(where_snippets)
1598
1599 queries = [
1600 {'cmd': cmd, 'args': vals}
1601 ]
1602
1603
1604
1605 return queries
1606
1607
1608
1610
1611 if search_term is None:
1612 return []
1613
1614
1615 queries = self._generate_simple_query(search_term)
1616 if len(queries) > 0:
1617 return queries
1618
1619 _log.debug('[%s]: not a search term with a "suggestive" structure' % search_term)
1620
1621
1622 queries = []
1623
1624
1625 normalized = self._normalize_soundalikes(search_term)
1626
1627
1628
1629 if regex.match(u"^(\s|\t)*[a-zäöüßéáúóçøA-ZÄÖÜÇØ]+(\s|\t)*$", search_term, flags = regex.LOCALE | regex.UNICODE):
1630
1631 cmd = u"""
1632 SELECT DISTINCT ON (pk_identity) * from (
1633 select * from ((
1634 select vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.lastnames) ~* lower(%s)
1635 ) union all (
1636 -- first name
1637 select vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s)
1638 ) union all (
1639 -- anywhere in name
1640 select vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames || n.lastnames || coalesce(n.preferred, '')) ~* lower(%s)
1641 )) as super_list order by lastnames, firstnames, dob
1642 ) as sorted_list"""
1643 tmp = normalized.strip()
1644 args = []
1645 args.append(_('last name'))
1646 args.append('^' + tmp)
1647 args.append(_('first name'))
1648 args.append('^' + tmp)
1649 args.append(_('any name part'))
1650 args.append(tmp)
1651
1652 queries.append ({
1653 'cmd': cmd,
1654 'args': args
1655 })
1656 return queries
1657
1658
1659 parts_list = regex.split(u",|;", normalized)
1660
1661
1662 if len(parts_list) == 1:
1663
1664 sub_parts_list = regex.split(u"\s*|\t*", normalized)
1665
1666
1667 date_count = 0
1668 name_parts = []
1669 for part in sub_parts_list:
1670
1671
1672 if regex.search(u"\d", part, flags = regex.LOCALE | regex.UNICODE):
1673 date_count = date_count + 1
1674 date_part = part
1675 else:
1676 name_parts.append(part)
1677
1678
1679 if len(sub_parts_list) == 2:
1680
1681 if date_count == 0:
1682
1683 queries.append ({
1684 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and n.firstnames ~ %s AND n.lastnames ~ %s",
1685 'args': [_('name: first-last'), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES)]
1686 })
1687 queries.append ({
1688 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s)",
1689 'args': [_('name: first-last'), '^' + name_parts[0], '^' + name_parts[1]]
1690 })
1691
1692 queries.append ({
1693 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and n.firstnames ~ %s AND n.lastnames ~ %s",
1694 'args': [_('name: last-first'), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES)]
1695 })
1696 queries.append ({
1697 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s)",
1698 'args': [_('name: last-first'), '^' + name_parts[1], '^' + name_parts[0]]
1699 })
1700
1701 queries.append ({
1702 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames || n.lastnames) ~* lower(%s) AND lower(n.firstnames || n.lastnames) ~* lower(%s)",
1703 'args': [_('name'), name_parts[0], name_parts[1]]
1704 })
1705 return queries
1706
1707 _log.error("don't know how to generate queries for [%s]" % search_term)
1708 return queries
1709
1710
1711 if len(sub_parts_list) == 3:
1712
1713 if date_count == 1:
1714
1715 queries.append ({
1716 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and n.firstnames ~ %s AND n.lastnames ~ %s AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)",
1717 'args': [_('names: first-last, date of birth'), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES), date_part.replace(u',', u'.')]
1718 })
1719 queries.append ({
1720 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s) AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)",
1721 'args': [_('names: first-last, date of birth'), '^' + name_parts[0], '^' + name_parts[1], date_part.replace(u',', u'.')]
1722 })
1723
1724 queries.append ({
1725 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and n.firstnames ~ %s AND n.lastnames ~ %s AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)",
1726 'args': [_('names: last-first, date of birth'), '^' + gmTools.capitalize(name_parts[1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0], mode=gmTools.CAPS_NAMES), date_part.replace(u',', u'.')]
1727 })
1728 queries.append ({
1729 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames) ~* lower(%s) AND lower(n.lastnames) ~* lower(%s) AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)",
1730 'args': [_('names: last-first, dob'), '^' + name_parts[1], '^' + name_parts[0], date_part.replace(u',', u'.')]
1731 })
1732
1733 queries.append ({
1734 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and lower(n.firstnames || n.lastnames) ~* lower(%s) AND lower(n.firstnames || n.lastnames) ~* lower(%s) AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)",
1735 'args': [_('name, date of birth'), name_parts[0], name_parts[1], date_part.replace(u',', u'.')]
1736 })
1737 return queries
1738
1739 queries.append(self._generate_dumb_brute_query(search_term))
1740 return queries
1741
1742
1743 queries.append(self._generate_dumb_brute_query(search_term))
1744 return queries
1745
1746
1747 else:
1748
1749 date_parts = []
1750 name_parts = []
1751 name_count = 0
1752 for part in parts_list:
1753
1754 if regex.search(u"\d+", part, flags = regex.LOCALE | regex.UNICODE):
1755
1756 date_parts.append(part)
1757 else:
1758 tmp = part.strip()
1759 tmp = regex.split(u"\s*|\t*", tmp)
1760 name_count = name_count + len(tmp)
1761 name_parts.append(tmp)
1762
1763 where_parts = []
1764
1765
1766 if (len(name_parts) == 1) and (name_count == 2):
1767
1768 where_parts.append ({
1769 'conditions': u"firstnames ~ %s and lastnames ~ %s",
1770 'args': [_('names: first last'), '^' + gmTools.capitalize(name_parts[0][0], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0][1], mode=gmTools.CAPS_NAMES)]
1771 })
1772 where_parts.append ({
1773 'conditions': u"lower(firstnames) ~* lower(%s) and lower(lastnames) ~* lower(%s)",
1774 'args': [_('names: first last'), '^' + name_parts[0][0], '^' + name_parts[0][1]]
1775 })
1776
1777 where_parts.append ({
1778 'conditions': u"firstnames ~ %s and lastnames ~ %s",
1779 'args': [_('names: last, first'), '^' + gmTools.capitalize(name_parts[0][1], mode=gmTools.CAPS_NAMES), '^' + gmTools.capitalize(name_parts[0][0], mode=gmTools.CAPS_NAMES)]
1780 })
1781 where_parts.append ({
1782 'conditions': u"lower(firstnames) ~* lower(%s) and lower(lastnames) ~* lower(%s)",
1783 'args': [_('names: last, first'), '^' + name_parts[0][1], '^' + name_parts[0][0]]
1784 })
1785
1786 where_parts.append ({
1787 'conditions': u"lower(firstnames || lastnames) ~* lower(%s) OR lower(firstnames || lastnames) ~* lower(%s)",
1788 'args': [_('name'), name_parts[0][0], name_parts[0][1]]
1789 })
1790
1791
1792 elif len(name_parts) == 2:
1793
1794 where_parts.append ({
1795 'conditions': u"firstnames ~ %s AND lastnames ~ %s",
1796 'args': [_('name: last, first'), '^' + ' '.join(map(gmTools.capitalize, name_parts[1])), '^' + ' '.join(map(gmTools.capitalize, name_parts[0]))]
1797 })
1798 where_parts.append ({
1799 'conditions': u"lower(firstnames) ~* lower(%s) AND lower(lastnames) ~* lower(%s)",
1800 'args': [_('name: last, first'), '^' + ' '.join(name_parts[1]), '^' + ' '.join(name_parts[0])]
1801 })
1802
1803 where_parts.append ({
1804 'conditions': u"firstnames ~ %s AND lastnames ~ %s",
1805 'args': [_('name: last, first'), '^' + ' '.join(map(gmTools.capitalize, name_parts[0])), '^' + ' '.join(map(gmTools.capitalize, name_parts[1]))]
1806 })
1807 where_parts.append ({
1808 'conditions': u"lower(firstnames) ~* lower(%s) AND lower(lastnames) ~* lower(%s)",
1809 'args': [_('name: last, first'), '^' + ' '.join(name_parts[0]), '^' + ' '.join(name_parts[1])]
1810 })
1811
1812 where_parts.append ({
1813 'conditions': u"lower(firstnames || lastnames) ~* lower(%s) AND lower(firstnames || lastnames) ~* lower(%s)",
1814 'args': [_('name'), ' '.join(name_parts[0]), ' '.join(name_parts[1])]
1815 })
1816
1817
1818 else:
1819
1820 if len(name_parts) == 1:
1821 for part in name_parts[0]:
1822 where_parts.append ({
1823 'conditions': u"lower(firstnames || lastnames) ~* lower(%s)",
1824 'args': [_('name'), part]
1825 })
1826 else:
1827 tmp = []
1828 for part in name_parts:
1829 tmp.append(' '.join(part))
1830 for part in tmp:
1831 where_parts.append ({
1832 'conditions': u"lower(firstnames || lastnames) ~* lower(%s)",
1833 'args': [_('name'), part]
1834 })
1835
1836
1837
1838 if len(date_parts) == 1:
1839 if len(where_parts) == 0:
1840 where_parts.append ({
1841 'conditions': u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)",
1842 'args': [_('date of birth'), date_parts[0].replace(u',', u'.')]
1843 })
1844 if len(where_parts) > 0:
1845 where_parts[0]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)"
1846 where_parts[0]['args'].append(date_parts[0].replace(u',', u'.'))
1847 where_parts[0]['args'][0] += u', ' + _('date of birth')
1848 if len(where_parts) > 1:
1849 where_parts[1]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)"
1850 where_parts[1]['args'].append(date_parts[0].replace(u',', u'.'))
1851 where_parts[1]['args'][0] += u', ' + _('date of birth')
1852 elif len(date_parts) > 1:
1853 if len(where_parts) == 0:
1854 where_parts.append ({
1855 'conditions': u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp witih time zone) AND dem.date_trunc_utc('day'::text, dem.identity.deceased) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)",
1856 'args': [_('date of birth/death'), date_parts[0].replace(u',', u'.'), date_parts[1].replace(u',', u'.')]
1857 })
1858 if len(where_parts) > 0:
1859 where_parts[0]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) AND dem.date_trunc_utc('day'::text, dem.identity.deceased) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)",
1860 where_parts[0]['args'].append(date_parts[0].replace(u',', u'.'), date_parts[1].replace(u',', u'.'))
1861 where_parts[0]['args'][0] += u', ' + _('date of birth/death')
1862 if len(where_parts) > 1:
1863 where_parts[1]['conditions'] += u" AND dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone) AND dem.date_trunc_utc('day'::text, dem.identity.deceased) = dem.date_trunc_utc('day'::text, %s::timestamp with time zone)",
1864 where_parts[1]['args'].append(date_parts[0].replace(u',', u'.'), date_parts[1].replace(u',', u'.'))
1865 where_parts[1]['args'][0] += u', ' + _('date of birth/death')
1866
1867
1868 for where_part in where_parts:
1869 queries.append ({
1870 'cmd': u"select *, %%s::text as match_type from dem.v_basic_person where %s" % where_part['conditions'],
1871 'args': where_part['args']
1872 })
1873 return queries
1874
1875 return []
1876
1878
1879 _log.debug('_generate_dumb_brute_query("%s")' % search_term)
1880
1881 where_clause = ''
1882 args = []
1883
1884 for arg in search_term.strip().split():
1885 where_clause += u' and lower(vbp.title || vbp.firstnames || vbp.lastnames) ~* lower(%s)'
1886 args.append(arg)
1887
1888 query = u"""
1889 select distinct on (pk_identity) * from (
1890 select
1891 vbp.*, '%s'::text as match_type
1892 from
1893 dem.v_basic_person vbp,
1894 dem.names n
1895 where
1896 vbp.pk_identity = n.id_identity
1897 %s
1898 order by
1899 lastnames,
1900 firstnames,
1901 dob
1902 ) as ordered_list""" % (_('full name'), where_clause)
1903
1904 return ({'cmd': query, 'args': args})
1905
1906
1907
1910 gmMatchProvider.cMatchProvider_SQL2.__init__(
1911 self,
1912 queries = [
1913 u"""select
1914 pk_staff,
1915 short_alias || ' (' || coalesce(title, '') || firstnames || ' ' || lastnames || ')',
1916 1
1917 from dem.v_staff
1918 where
1919 is_active and (
1920 short_alias %(fragment_condition)s or
1921 firstnames %(fragment_condition)s or
1922 lastnames %(fragment_condition)s or
1923 db_user %(fragment_condition)s
1924 )"""
1925 ]
1926 )
1927 self.setThresholds(1, 2, 3)
1928
1929
1930
1931 -def create_name(pk_person, firstnames, lastnames, active=False):
1932 queries = [{
1933 'cmd': u"select dem.add_name(%s, %s, %s, %s)",
1934 'args': [pk_person, firstnames, lastnames, active]
1935 }]
1936 rows, idx = gmPG2.run_rw_queries(queries=queries, return_data=True)
1937 name = cPersonName(aPK_obj = rows[0][0])
1938 return name
1939
1940 -def create_identity(gender=None, dob=None, lastnames=None, firstnames=None):
1941
1942 cmd1 = u"""insert into dem.identity (gender, dob) values (%s, %s)"""
1943
1944 cmd2 = u"""
1945 insert into dem.names (
1946 id_identity, lastnames, firstnames
1947 ) values (
1948 currval('dem.identity_pk_seq'), coalesce(%s, 'xxxDEFAULTxxx'), coalesce(%s, 'xxxDEFAULTxxx')
1949 )"""
1950
1951 rows, idx = gmPG2.run_rw_queries (
1952 queries = [
1953 {'cmd': cmd1, 'args': [gender, dob]},
1954 {'cmd': cmd2, 'args': [lastnames, firstnames]},
1955 {'cmd': u"select currval('dem.identity_pk_seq')"}
1956 ],
1957 return_data = True
1958 )
1959 return cIdentity(aPK_obj=rows[0][0])
1960
1962 cmd1 = u"insert into dem.identity(gender) values('xxxDEFAULTxxx')"
1963 cmd2 = u"select currval('dem.identity_pk_seq')"
1964
1965 rows, idx = gmPG2.run_rw_queries (
1966 queries = [
1967 {'cmd': cmd1},
1968 {'cmd': cmd2}
1969 ],
1970 return_data = True
1971 )
1972 return gmDemographicRecord.cIdentity(aPK_obj = rows[0][0])
1973
2000
2015
2017 """Text mode UI function to ask for patient."""
2018
2019 person_searcher = cPatientSearcher_SQL()
2020
2021 while True:
2022 search_fragment = prompted_input("\nEnter person search term or leave blank to exit")
2023
2024 if search_fragment in ['exit', 'quit', 'bye', None]:
2025 print "user cancelled patient search"
2026 return None
2027
2028 pats = person_searcher.get_patients(search_term = search_fragment)
2029
2030 if (pats is None) or (len(pats) == 0):
2031 print "No patient matches the query term."
2032 print ""
2033 continue
2034
2035 if len(pats) > 1:
2036 print "Several patients match the query term:"
2037 print ""
2038 for pat in pats:
2039 print pat
2040 print ""
2041 continue
2042
2043 return pats[0]
2044
2045 return None
2046
2047
2048
2059
2060 map_gender2mf = {
2061 'm': u'm',
2062 'f': u'f',
2063 'tf': u'f',
2064 'tm': u'm',
2065 'h': u'mf'
2066 }
2067
2068
2069 map_gender2symbol = {
2070 'm': u'\u2642',
2071 'f': u'\u2640',
2072 'tf': u'\u26A5\u2640',
2073 'tm': u'\u26A5\u2642',
2074 'h': u'\u26A5'
2075
2076
2077
2078 }
2079
2100
2102 """Try getting the gender for the given first name."""
2103
2104 if firstnames is None:
2105 return None
2106
2107 rows, idx = gmPG2.run_ro_queries(queries = [{
2108 'cmd': u"select gender from dem.name_gender_map where name ilike %(fn)s limit 1",
2109 'args': {'fn': firstnames}
2110 }])
2111
2112 if len(rows) == 0:
2113 return None
2114
2115 return rows[0][0]
2116
2118 if active_only:
2119 cmd = u"select * from dem.v_staff where is_active order by can_login desc, short_alias asc"
2120 else:
2121 cmd = u"select * from dem.v_staff order by can_login desc, is_active desc, short_alias asc"
2122 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True)
2123 staff_list = []
2124 for row in rows:
2125 obj_row = {
2126 'idx': idx,
2127 'data': row,
2128 'pk_field': 'pk_staff'
2129 }
2130 staff_list.append(cStaff(row=obj_row))
2131 return staff_list
2132
2134 return [ cIdentity(aPK_obj = pk) for pk in pks ]
2135
2139
2143
2144
2145
2146 if __name__ == '__main__':
2147
2148 import datetime
2149
2150 gmI18N.activate_locale()
2151 gmI18N.install_domain()
2152 gmDateTime.init()
2153
2154
2175
2177 dto = cDTO_person()
2178 dto.firstnames = 'Sepp'
2179 dto.lastnames = 'Herberger'
2180 dto.gender = 'male'
2181 dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone)
2182 print dto
2183
2184 print dto['firstnames']
2185 print dto['lastnames']
2186 print dto['gender']
2187 print dto['dob']
2188
2189 for key in dto.keys():
2190 print key
2191
2204
2205
2211
2212
2225
2227
2228 print '\n\nCreating identity...'
2229 new_identity = create_identity(gender='m', dob='2005-01-01', lastnames='test lastnames', firstnames='test firstnames')
2230 print 'Identity created: %s' % new_identity
2231
2232 print '\nSetting title and gender...'
2233 new_identity['title'] = 'test title';
2234 new_identity['gender'] = 'f';
2235 new_identity.save_payload()
2236 print 'Refetching identity from db: %s' % cIdentity(aPK_obj=new_identity['pk_identity'])
2237
2238 print '\nGetting all names...'
2239 for a_name in new_identity.get_names():
2240 print a_name
2241 print 'Active name: %s' % (new_identity.get_active_name())
2242 print 'Setting nickname...'
2243 new_identity.set_nickname(nickname='test nickname')
2244 print 'Refetching all names...'
2245 for a_name in new_identity.get_names():
2246 print a_name
2247 print 'Active name: %s' % (new_identity.get_active_name())
2248
2249 print '\nIdentity occupations: %s' % new_identity['occupations']
2250 print 'Creating identity occupation...'
2251 new_identity.link_occupation('test occupation')
2252 print 'Identity occupations: %s' % new_identity['occupations']
2253
2254 print '\nIdentity addresses: %s' % new_identity.get_addresses()
2255 print 'Creating identity address...'
2256
2257 new_identity.link_address (
2258 number = 'test 1234',
2259 street = 'test street',
2260 postcode = 'test postcode',
2261 urb = 'test urb',
2262 state = 'SN',
2263 country = 'DE'
2264 )
2265 print 'Identity addresses: %s' % new_identity.get_addresses()
2266
2267 print '\nIdentity communications: %s' % new_identity.get_comm_channels()
2268 print 'Creating identity communication...'
2269 new_identity.link_comm_channel('homephone', '1234566')
2270 print 'Identity communications: %s' % new_identity.get_comm_channels()
2271
2273 searcher = cPatientSearcher_SQL()
2274
2275 print "testing _normalize_soundalikes()"
2276 print "--------------------------------"
2277
2278 data = [u'Krüger', u'Krueger', u'Kruger', u'Überle', u'Böger', u'Boger', u'Öder', u'Ähler', u'Däler', u'Großer', u'müller', u'Özdemir', u'özdemir']
2279 for name in data:
2280 print '%s: %s' % (name, searcher._normalize_soundalikes(name))
2281
2282 raw_input('press [ENTER] to continue')
2283 print "============"
2284
2285 print "testing _generate_simple_query()"
2286 print "----------------------------"
2287 data = ['51234', '1 134 153', '#13 41 34', '#3-AFY322.4', '22-04-1906', '1235/32/3525', ' , johnny']
2288 for fragment in data:
2289 print "fragment:", fragment
2290 qs = searcher._generate_simple_query(fragment)
2291 for q in qs:
2292 print " match on:", q['args'][0]
2293 print " query :", q['cmd']
2294 raw_input('press [ENTER] to continue')
2295 print "============"
2296
2297 print "testing _generate_queries_from_dto()"
2298 print "------------------------------------"
2299 dto = cDTO_person()
2300 dto.gender = 'm'
2301 dto.lastnames = 'Kirk'
2302 dto.firstnames = 'James'
2303 dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone)
2304 q = searcher._generate_queries_from_dto(dto)[0]
2305 print "dto:", dto
2306 print " match on:", q['args'][0]
2307 print " query:", q['cmd']
2308
2309 raw_input('press [ENTER] to continue')
2310 print "============"
2311
2312 print "testing _generate_queries_de()"
2313 print "------------------------------"
2314 qs = searcher._generate_queries_de('Kirk, James')
2315 for q in qs:
2316 print " match on:", q['args'][0]
2317 print " query :", q['cmd']
2318 print " args :", q['args']
2319 raw_input('press [ENTER] to continue')
2320 print "============"
2321
2322 qs = searcher._generate_queries_de(u'müller')
2323 for q in qs:
2324 print " match on:", q['args'][0]
2325 print " query :", q['cmd']
2326 print " args :", q['args']
2327 raw_input('press [ENTER] to continue')
2328 print "============"
2329
2330 qs = searcher._generate_queries_de(u'özdemir')
2331 for q in qs:
2332 print " match on:", q['args'][0]
2333 print " query :", q['cmd']
2334 print " args :", q['args']
2335 raw_input('press [ENTER] to continue')
2336 print "============"
2337
2338 qs = searcher._generate_queries_de(u'Özdemir')
2339 for q in qs:
2340 print " match on:", q['args'][0]
2341 print " query :", q['cmd']
2342 print " args :", q['args']
2343 raw_input('press [ENTER] to continue')
2344 print "============"
2345
2346 print "testing _generate_dumb_brute_query()"
2347 print "------------------------------------"
2348 q = searcher._generate_dumb_brute_query('Kirk, James Tiberius')
2349 print " match on:", q['args'][0]
2350 print " query:", q['cmd']
2351 print " args:", q['args']
2352
2353 raw_input('press [ENTER] to continue')
2354
2365
2366
2367
2368
2369
2375
2376 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2377
2378
2379
2380
2381
2382
2383
2384 test_current_provider()
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477