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 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
379 return self._payload[self._idx['pk_identity']]
381 raise AttributeError('setting ID of identity is not allowed')
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
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
422 raise AttributeError('setting is_patient status of identity is not allowed')
423
424 is_patient = property(_get_is_patient, _set_is_patient)
425
426
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
448 return []
449
450 names = [ cPersonName(row = {'idx': idx, 'data': r, 'pk_field': 'pk_name'}) for r in rows ]
451 return names
452
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
479 - def add_name(self, firstnames, lastnames, active=True):
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
497
498
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
511
512
513
514
515
516
517
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
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
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
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
588 else:
589 row = rows[0]
590 if comment is not None:
591
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
598 - def update_external_id(self, pk_id=None, type=None, value=None, issuer=None, comment=None):
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
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
663
664
665 queries.append ({
666 'cmd': u'update dem.names set active = False where id_identity = %(old_pat)s',
667 'args': args
668 })
669
670
671 FKs = gmPG2.get_foreign_keys2column (
672 schema = u'dem',
673 table = u'identity',
674 column = u'pk'
675 )
676
677
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
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
717 - def export_as_gdt(self, filename=None, encoding='iso-8859-15', external_id_type=None):
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
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
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
848
849
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
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
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
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
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
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
964 id_new_relative = create_dummy_identity()
965
966 relative = cIdentity(aPK_obj=id_new_relative)
967
968
969 relative.add_name( '**?**', self.get_names()['lastnames'])
970
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
983
984 self.set_relative(None, relation)
985
986
987
1004
1005 - def dob_in_range(self, min_distance=u'1 week', max_distance=u'1 week'):
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
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
1027
1030
1031 messages = property(_get_messages, _set_messages)
1032
1035
1036
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 """
1065 - def __init__(self, aPK_obj=None, row=None):
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 """
1109 - def __init__(self, patient=None, forced_reload=False):
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
1118 try:
1119 tmp = self.patient
1120 except AttributeError:
1121 self.patient = gmNull.cNull()
1122 self.__register_interests()
1123
1124
1125
1126 self.__lock_depth = 0
1127
1128 self.__pre_selection_callbacks = []
1129
1130
1131 if patient is None:
1132 return None
1133
1134
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
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
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
1157 if (self.patient['pk_identity'] == patient['pk_identity']) and not forced_reload:
1158 return None
1159
1160
1161 _log.debug('patient change [%s] -> [%s] requested', self.patient['pk_identity'], patient['pk_identity'])
1162
1163
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
1178
1182
1183
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
1195 raise AttributeError(u'invalid to set <connected> state')
1196
1197 connected = property(_get_connected, _set_connected)
1198
1200 return (self.__lock_depth > 0)
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
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
1265
1267 if attribute == 'patient':
1268 raise AttributeError
1269 if not isinstance(self.patient, gmNull.cNull):
1270 return getattr(self.patient, attribute)
1271
1272
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
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
1299
1300 - def get_patients(self, search_term = None, a_locale = None, dto = None):
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
1306 - def get_identities(self, search_term = None, a_locale = None, dto = None):
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
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
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
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
1340 identities = []
1341
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
1366
1368 """Transform some characters into a regex."""
1369 if aString.strip() == u'':
1370 return aString
1371
1372
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
1382
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
1388 normalized = normalized.replace(u'v', u'***DUMMY***')
1389 normalized = normalized.replace(u'f', u'***DUMMY***')
1390 normalized = normalized.replace(u'ph', u'***DUMMY***')
1391 normalized = normalized.replace(u'***DUMMY***', u'(v|f|ph)')
1392
1393
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
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
1412
1413 _log.debug('[%s] -> [%s]' % (aString, normalized))
1414
1415 return normalized
1416
1417
1418
1419
1420
1421
1422
1424 """Compose queries if search term seems unambigous."""
1425 queries = []
1426
1427
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
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
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
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
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
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
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
1516
1517
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
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
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
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
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
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
1606
1607 return queries
1608
1609
1610
1612
1613 if search_term is None:
1614 return []
1615
1616
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
1624 queries = []
1625
1626
1627 normalized = self._normalize_soundalikes(search_term)
1628
1629
1630
1631 if regex.match(u"^(\s|\t)*[a-zäöüßéáúóçøA-ZÄÖÜÇØ]+(\s|\t)*$", search_term, flags = regex.LOCALE | regex.UNICODE):
1632
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
1661 parts_list = regex.split(u",|;", normalized)
1662
1663
1664 if len(parts_list) == 1:
1665
1666 sub_parts_list = regex.split(u"\s*|\t*", normalized)
1667
1668
1669 date_count = 0
1670 name_parts = []
1671 for part in sub_parts_list:
1672
1673
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
1681 if len(sub_parts_list) == 2:
1682
1683 if date_count == 0:
1684
1685 queries.append ({
1686 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and 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
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
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
1709 _log.error("don't know how to generate queries for [%s]" % search_term)
1710 return queries
1711
1712
1713 if len(sub_parts_list) == 3:
1714
1715 if date_count == 1:
1716
1717 queries.append ({
1718 'cmd': u"SELECT DISTINCT ON (id_identity) vbp.*, %s::text as match_type from dem.v_basic_person vbp, dem.names n WHERE vbp.pk_identity = n.id_identity and 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
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
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
1741 queries.append(self._generate_dumb_brute_query(search_term))
1742 return queries
1743
1744
1745 queries.append(self._generate_dumb_brute_query(search_term))
1746 return queries
1747
1748
1749 else:
1750
1751 date_parts = []
1752 name_parts = []
1753 name_count = 0
1754 for part in parts_list:
1755
1756 if regex.search(u"\d+", part, flags = regex.LOCALE | regex.UNICODE):
1757
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
1767
1768 if (len(name_parts) == 1) and (name_count == 2):
1769
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
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
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
1794 elif len(name_parts) == 2:
1795
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
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
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
1820 else:
1821
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
1839
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
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
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
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
1932
1933 -def create_name(pk_person, firstnames, lastnames, active=False):
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
1942 -def create_identity(gender=None, dob=None, lastnames=None, firstnames=None):
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
2002
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
2050
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
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
2078
2079
2080 }
2081
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
2141
2145
2146
2147
2148 if __name__ == '__main__':
2149
2150 import datetime
2151
2152 gmI18N.activate_locale()
2153 gmI18N.install_domain()
2154 gmDateTime.init()
2155
2156
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
2206
2207
2213
2214
2227
2229
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
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
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
2367
2368
2369
2370
2371
2377
2378 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2379
2380
2381
2382
2383
2384
2385
2386 test_current_provider()
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
3478
3479