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 fk_primary_provider = %(pk_primary_provider)s,
358 comment = gm.nullify_empty_string(%(comment)s)
359 where
360 pk = %(pk_identity)s and
361 xmin = %(xmin_identity)s""",
362 u"""select xmin_identity from dem.v_basic_person where pk_identity = %(pk_identity)s"""
363 ]
364 _updatable_fields = [
365 "title",
366 "dob",
367 "tob",
368 "cob",
369 "gender",
370 "pk_marital_status",
371 "karyotype",
372 "pupic",
373 'deceased',
374 'emergency_contact',
375 'pk_emergency_contact',
376 'pk_primary_provider',
377 'comment'
378 ]
379
381 return self._payload[self._idx['pk_identity']]
383 raise AttributeError('setting ID of identity is not allowed')
384 ID = property(_get_ID, _set_ID)
385
387
388 if attribute == 'dob':
389 if value is not None:
390
391 if isinstance(value, pyDT.datetime):
392 if value.tzinfo is None:
393 raise ValueError('datetime.datetime instance is lacking a time zone: [%s]' % dt.isoformat())
394 else:
395 raise TypeError, '[%s]: type [%s] (%s) invalid for attribute [dob], must be datetime.datetime or None' % (self.__class__.__name__, type(value), value)
396
397
398 if self._payload[self._idx['dob']] is not None:
399 old_dob = self._payload[self._idx['dob']].strftime('%Y %m %d %H %M %S')
400 new_dob = value.strftime('%Y %m %d %H %M %S')
401 if new_dob == old_dob:
402 return
403
404 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
405
408
410 cmd = u"""
411 select exists (
412 select 1
413 from clin.v_emr_journal
414 where
415 pk_patient = %(pat)s
416 and
417 soap_cat is not null
418 )"""
419 args = {'pat': self._payload[self._idx['pk_identity']]}
420 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
421 return rows[0][0]
422
424 raise AttributeError('setting is_patient status of identity is not allowed')
425
426 is_patient = property(_get_is_patient, _set_is_patient)
427
428
429
431 for name in self.get_names():
432 if name['active_name'] is True:
433 return name
434
435 _log.error('cannot retrieve active name for patient [%s]' % self._payload[self._idx['pk_identity']])
436 return None
437
439 cmd = u"select * from dem.v_person_names where pk_identity = %(pk_pat)s"
440 rows, idx = gmPG2.run_ro_queries (
441 queries = [{
442 'cmd': cmd,
443 'args': {'pk_pat': self._payload[self._idx['pk_identity']]}
444 }],
445 get_col_idx = True
446 )
447
448 if len(rows) == 0:
449
450 return []
451
452 names = [ cPersonName(row = {'idx': idx, 'data': r, 'pk_field': 'pk_name'}) for r in rows ]
453 return names
454
463
465 return '%(sex)s%(title)s %(last)s, %(first)s%(nick)s' % {
466 'last': self._payload[self._idx['lastnames']],
467 'first': self._payload[self._idx['firstnames']],
468 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u' (%s)', u'%s'),
469 'sex': map_gender2salutation(self._payload[self._idx['gender']]),
470 'title': gmTools.coalesce(self._payload[self._idx['title']], u'', u' %s', u'%s')
471 }
472
474 return '%(last)s,%(title)s %(first)s%(nick)s' % {
475 'last': self._payload[self._idx['lastnames']],
476 'title': gmTools.coalesce(self._payload[self._idx['title']], u'', u' %s', u'%s'),
477 'first': self._payload[self._idx['firstnames']],
478 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u' (%s)', u'%s')
479 }
480
481 - def add_name(self, firstnames, lastnames, active=True):
482 """Add a name.
483
484 @param firstnames The first names.
485 @param lastnames The last names.
486 @param active When True, the new name will become the active one (hence setting other names to inactive)
487 @type active A types.BooleanType instance
488 """
489 name = create_name(self.ID, firstnames, lastnames, active)
490 if active:
491 self.refetch_payload()
492 return name
493
495 cmd = u"delete from dem.names where id = %(name)s and id_identity = %(pat)s"
496 args = {'name': name['pk_name'], 'pat': self.ID}
497 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
498
499
500
501
503 """
504 Set the nickname. Setting the nickname only makes sense for the currently
505 active name.
506 @param nickname The preferred/nick/warrior name to set.
507 """
508 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': u"select dem.set_nickname(%s, %s)", 'args': [self.ID, nickname]}])
509 self.refetch_payload()
510 return True
511
512
513
514
515
516
517
518
519
520
521 - def add_external_id(self, type_name=None, value=None, issuer=None, comment=None, pk_type=None):
522 """Adds an external ID to the patient.
523
524 creates ID type if necessary
525 """
526
527
528 if pk_type is not None:
529 cmd = u"""
530 select * from dem.v_external_ids4identity where
531 pk_identity = %(pat)s and
532 pk_type = %(pk_type)s and
533 value = %(val)s"""
534 else:
535
536 if issuer is None:
537 cmd = u"""
538 select * from dem.v_external_ids4identity where
539 pk_identity = %(pat)s and
540 name = %(name)s and
541 value = %(val)s"""
542 else:
543 cmd = u"""
544 select * from dem.v_external_ids4identity where
545 pk_identity = %(pat)s and
546 name = %(name)s and
547 value = %(val)s and
548 issuer = %(issuer)s"""
549 args = {
550 'pat': self.ID,
551 'name': type_name,
552 'val': value,
553 'issuer': issuer,
554 'pk_type': pk_type
555 }
556 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
557
558
559 if len(rows) == 0:
560
561 args = {
562 'pat': self.ID,
563 'val': value,
564 'type_name': type_name,
565 'pk_type': pk_type,
566 'issuer': issuer,
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)),
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 """
603 cmd = u"""
604 update dem.lnk_identity2ext_id set
605 fk_origin = (select dem.add_external_id_type(%(type)s, %(issuer)s)),
606 external_id = %(value)s,
607 comment = %(comment)s
608 where id = %(pk)s"""
609 args = {'pk': pk_id, 'value': value, 'type': type, 'issuer': issuer, 'comment': comment}
610 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
611
613 where_parts = ['pk_identity = %(pat)s']
614 args = {'pat': self.ID}
615
616 if id_type is not None:
617 where_parts.append(u'name = %(name)s')
618 args['name'] = id_type.strip()
619
620 if issuer is not None:
621 where_parts.append(u'issuer = %(issuer)s')
622 args['issuer'] = issuer.strip()
623
624 cmd = u"select * from dem.v_external_ids4identity where %s" % ' and '.join(where_parts)
625 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
626
627 return rows
628
630 cmd = u"""
631 delete from dem.lnk_identity2ext_id
632 where id_identity = %(pat)s and id = %(pk)s"""
633 args = {'pat': self.ID, 'pk': pk_ext_id}
634 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
635
637 """Merge another identity into this one.
638
639 Keep this one. Delete other one."""
640
641 if other_identity.ID == self.ID:
642 return True, None
643
644 curr_pat = gmCurrentPatient()
645 if curr_pat.connected:
646 if other_identity.ID == curr_pat.ID:
647 return False, _('Cannot merge active patient into another patient.')
648
649 queries = []
650 args = {'old_pat': other_identity.ID, 'new_pat': self.ID}
651
652
653 queries.append ({
654 '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)',
655 'args': args
656 })
657
658
659
660 queries.append ({
661 'cmd': u'update dem.names set active = False where id_identity = %(old_pat)s',
662 'args': args
663 })
664
665
666 FKs = gmPG2.get_foreign_keys2column (
667 schema = u'dem',
668 table = u'identity',
669 column = u'pk'
670 )
671
672
673 cmd_template = u'update %s set %s = %%(new_pat)s where %s = %%(old_pat)s'
674 for FK in FKs:
675 queries.append ({
676 'cmd': cmd_template % (FK['referencing_table'], FK['referencing_column'], FK['referencing_column']),
677 'args': args
678 })
679
680
681 queries.append ({
682 'cmd': u'delete from dem.identity where pk = %(old_pat)s',
683 'args': args
684 })
685
686 _log.warning('identity [%s] is about to assimilate identity [%s]', self.ID, other_identity.ID)
687
688 gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, end_tx = True)
689
690 self.add_external_id (
691 type_name = u'merged GNUmed identity primary key',
692 value = u'GNUmed::pk::%s' % other_identity.ID,
693 issuer = u'GNUmed'
694 )
695
696 return True, None
697
698
700 cmd = u"""
701 insert into clin.waiting_list (fk_patient, urgency, comment, area, list_position)
702 values (
703 %(pat)s,
704 %(urg)s,
705 %(cmt)s,
706 %(area)s,
707 (select coalesce((max(list_position) + 1), 1) from clin.waiting_list)
708 )"""
709 args = {'pat': self.ID, 'urg': urgency, 'cmt': comment, 'area': zone}
710 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], verbose=True)
711
712 - def export_as_gdt(self, filename=None, encoding='iso-8859-15', external_id_type=None):
713
714 template = u'%s%s%s\r\n'
715
716 file = codecs.open (
717 filename = filename,
718 mode = 'wb',
719 encoding = encoding,
720 errors = 'strict'
721 )
722
723 file.write(template % (u'013', u'8000', u'6301'))
724 file.write(template % (u'013', u'9218', u'2.10'))
725 if external_id_type is None:
726 file.write(template % (u'%03d' % (9 + len(str(self.ID))), u'3000', self.ID))
727 else:
728 ext_ids = self.get_external_ids(id_type = external_id_type)
729 if len(ext_ids) > 0:
730 file.write(template % (u'%03d' % (9 + len(ext_ids[0]['value'])), u'3000', ext_ids[0]['value']))
731 file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['lastnames']])), u'3101', self._payload[self._idx['lastnames']]))
732 file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['firstnames']])), u'3102', self._payload[self._idx['firstnames']]))
733 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')))
734 file.write(template % (u'010', u'3110', gmXdtMappings.map_gender_gm2xdt[self._payload[self._idx['gender']]]))
735 file.write(template % (u'025', u'6330', 'GNUmed::9206::encoding'))
736 file.write(template % (u'%03d' % (9 + len(encoding)), u'6331', encoding))
737 if external_id_type is None:
738 file.write(template % (u'029', u'6332', u'GNUmed::3000::source'))
739 file.write(template % (u'017', u'6333', u'internal'))
740 else:
741 if len(ext_ids) > 0:
742 file.write(template % (u'029', u'6332', u'GNUmed::3000::source'))
743 file.write(template % (u'%03d' % (9 + len(external_id_type)), u'6333', external_id_type))
744
745 file.close()
746
747
748
750 cmd = u"select * from dem.v_person_jobs where pk_identity=%s"
751 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
752 return rows
753
755 """Link an occupation with a patient, creating the occupation if it does not exists.
756
757 @param occupation The name of the occupation to link the patient to.
758 """
759 if (activities is None) and (occupation is None):
760 return True
761
762 occupation = occupation.strip()
763 if len(occupation) == 0:
764 return True
765
766 if activities is not None:
767 activities = activities.strip()
768
769 args = {'act': activities, 'pat_id': self.pk_obj, 'job': occupation}
770
771 cmd = u"select activities from dem.v_person_jobs where pk_identity = %(pat_id)s and l10n_occupation = _(%(job)s)"
772 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
773
774 queries = []
775 if len(rows) == 0:
776 queries.append ({
777 'cmd': u"INSERT INTO dem.lnk_job2person (fk_identity, fk_occupation, activities) VALUES (%(pat_id)s, dem.create_occupation(%(job)s), %(act)s)",
778 'args': args
779 })
780 else:
781 if rows[0]['activities'] != activities:
782 queries.append ({
783 '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))",
784 'args': args
785 })
786
787 rows, idx = gmPG2.run_rw_queries(queries = queries)
788
789 return True
790
792 if occupation is None:
793 return True
794 occupation = occupation.strip()
795 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))"
796 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj, 'job': occupation}}])
797 return True
798
799
800
802 cmd = u"select * from dem.v_person_comms where pk_identity = %s"
803 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx = True)
804
805 filtered = rows
806
807 if comm_medium is not None:
808 filtered = []
809 for row in rows:
810 if row['comm_type'] == comm_medium:
811 filtered.append(row)
812
813 return [ gmDemographicRecord.cCommChannel(row = {
814 'pk_field': 'pk_lnk_identity2comm',
815 'data': r,
816 'idx': idx
817 }) for r in filtered
818 ]
819
820 - def link_comm_channel(self, comm_medium=None, url=None, is_confidential=False, pk_channel_type=None):
821 """Link a communication medium with a patient.
822
823 @param comm_medium The name of the communication medium.
824 @param url The communication resource locator.
825 @type url A types.StringType instance.
826 @param is_confidential Wether the data must be treated as confidential.
827 @type is_confidential A types.BooleanType instance.
828 """
829 comm_channel = gmDemographicRecord.create_comm_channel (
830 comm_medium = comm_medium,
831 url = url,
832 is_confidential = is_confidential,
833 pk_channel_type = pk_channel_type,
834 pk_identity = self.pk_obj
835 )
836 return comm_channel
837
843
844
845
847 cmd = u"select * from dem.v_pat_addresses where pk_identity=%s"
848 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx=True)
849 addresses = []
850 for r in rows:
851 addresses.append(gmDemographicRecord.cPatientAddress(row={'idx': idx, 'data': r, 'pk_field': 'pk_address'}))
852
853 filtered = addresses
854
855 if address_type is not None:
856 filtered = []
857 for adr in addresses:
858 if adr['address_type'] == address_type:
859 filtered.append(adr)
860
861 return filtered
862
863 - def link_address(self, number=None, street=None, postcode=None, urb=None, state=None, country=None, subunit=None, suburb=None, id_type=None):
864 """Link an address with a patient, creating the address if it does not exists.
865
866 @param number The number of the address.
867 @param street The name of the street.
868 @param postcode The postal code of the address.
869 @param urb The name of town/city/etc.
870 @param state The code of the state.
871 @param country The code of the country.
872 @param id_type The primary key of the address type.
873 """
874
875 adr = gmDemographicRecord.create_address (
876 country = country,
877 state = state,
878 urb = urb,
879 suburb = suburb,
880 postcode = postcode,
881 street = street,
882 number = number,
883 subunit = subunit
884 )
885
886
887 cmd = u"select * from dem.lnk_person_org_address where id_identity = %s and id_address = %s"
888 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj, adr['pk_address']]}])
889
890 if len(rows) == 0:
891 args = {'id': self.pk_obj, 'adr': adr['pk_address'], 'type': id_type}
892 if id_type is None:
893 cmd = u"""
894 insert into dem.lnk_person_org_address(id_identity, id_address)
895 values (%(id)s, %(adr)s)"""
896 else:
897 cmd = u"""
898 insert into dem.lnk_person_org_address(id_identity, id_address, id_type)
899 values (%(id)s, %(adr)s, %(type)s)"""
900 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
901 else:
902
903 if id_type is not None:
904 r = rows[0]
905 if r['id_type'] != id_type:
906 cmd = "update dem.lnk_person_org_address set id_type = %(type)s where id = %(id)s"
907 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'type': id_type, 'id': r['id']}}])
908
909 return adr
910
912 """Remove an address from the patient.
913
914 The address itself stays in the database.
915 The address can be either cAdress or cPatientAdress.
916 """
917 cmd = u"delete from dem.lnk_person_org_address where id_identity = %(person)s and id_address = %(adr)s"
918 args = {'person': self.pk_obj, 'adr': address['pk_address']}
919 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
920
921
922
924 cmd = u"""
925 select
926 t.description,
927 vbp.pk_identity as id,
928 title,
929 firstnames,
930 lastnames,
931 dob,
932 cob,
933 gender,
934 karyotype,
935 pupic,
936 pk_marital_status,
937 marital_status,+
938 xmin_identity,
939 preferred
940 from
941 dem.v_basic_person vbp, dem.relation_types t, dem.lnk_person2relative l
942 where
943 (
944 l.id_identity = %(pk)s and
945 vbp.pk_identity = l.id_relative and
946 t.id = l.id_relation_type
947 ) or (
948 l.id_relative = %(pk)s and
949 vbp.pk_identity = l.id_identity and
950 t.inverse = l.id_relation_type
951 )"""
952 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
953 if len(rows) == 0:
954 return []
955 return [(row[0], cIdentity(row = {'data': row[1:], 'idx':idx, 'pk_field': 'pk'})) for row in rows]
956
958
959 id_new_relative = create_dummy_identity()
960
961 relative = cIdentity(aPK_obj=id_new_relative)
962
963
964 relative.add_name( '**?**', self.get_names()['lastnames'])
965
966 if self._ext_cache.has_key('relatives'):
967 del self._ext_cache['relatives']
968 cmd = u"""
969 insert into dem.lnk_person2relative (
970 id_identity, id_relative, id_relation_type
971 ) values (
972 %s, %s, (select id from dem.relation_types where description = %s)
973 )"""
974 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [self.ID, id_new_relative, rel_type ]}])
975 return True
976
978
979 self.set_relative(None, relation)
980
981
982
1007
1008 - def dob_in_range(self, min_distance=u'1 week', max_distance=u'1 week'):
1009 cmd = u'select dem.dob_is_in_range(%(dob)s, %(min)s, %(max)s)'
1010 rows, idx = gmPG2.run_ro_queries (
1011 queries = [{
1012 'cmd': cmd,
1013 'args': {'dob': self['dob'], 'min': min_distance, 'max': max_distance}
1014 }]
1015 )
1016 return rows[0][0]
1017
1018
1019
1021 cmd = u'select * from clin.v_most_recent_encounters where pk_patient=%s'
1022 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self._payload[self._idx['pk_identity']]]}])
1023 if len(rows) > 0:
1024 return rows[0]
1025 else:
1026 return None
1027
1030
1033
1034 messages = property(_get_messages, _set_messages)
1035
1038
1039
1040
1042 """Format patient demographics into patient specific path name fragment."""
1043 return '%s-%s%s-%s' % (
1044 self._payload[self._idx['lastnames']].replace(u' ', u'_'),
1045 self._payload[self._idx['firstnames']].replace(u' ', u'_'),
1046 gmTools.coalesce(self._payload[self._idx['preferred']], u'', template_initial = u'-(%s)'),
1047 self.get_formatted_dob(format = '%Y-%m-%d', encoding = gmI18N.get_encoding())
1048 )
1049
1051 """Represents a staff member which is a person.
1052
1053 - a specializing subclass of cIdentity turning it into a staff member
1054 """
1058
1061
1063 """Represents a person which is a patient.
1064
1065 - a specializing subclass of cIdentity turning it into a patient
1066 - its use is to cache subobjects like EMR and document folder
1067 """
1068 - def __init__(self, aPK_obj=None, row=None):
1069 cIdentity.__init__(self, aPK_obj=aPK_obj, row=row)
1070 self.__db_cache = {}
1071 self.__emr_access_lock = threading.Lock()
1072
1074 """Do cleanups before dying.
1075
1076 - note that this may be called in a thread
1077 """
1078 if self.__db_cache.has_key('clinical record'):
1079 self.__db_cache['clinical record'].cleanup()
1080 if self.__db_cache.has_key('document folder'):
1081 self.__db_cache['document folder'].cleanup()
1082 cIdentity.cleanup(self)
1083
1085 if not self.__emr_access_lock.acquire(False):
1086 raise AttributeError('cannot access EMR')
1087 try:
1088 emr = self.__db_cache['clinical record']
1089 self.__emr_access_lock.release()
1090 return emr
1091 except KeyError:
1092 pass
1093
1094 self.__db_cache['clinical record'] = gmClinicalRecord.cClinicalRecord(aPKey = self._payload[self._idx['pk_identity']])
1095 self.__emr_access_lock.release()
1096 return self.__db_cache['clinical record']
1097
1099 try:
1100 return self.__db_cache['document folder']
1101 except KeyError:
1102 pass
1103
1104 self.__db_cache['document folder'] = gmDocuments.cDocumentFolder(aPKey = self._payload[self._idx['pk_identity']])
1105 return self.__db_cache['document folder']
1106
1108 """Patient Borg to hold currently active patient.
1109
1110 There may be many instances of this but they all share state.
1111 """
1112 - def __init__(self, patient=None, forced_reload=False):
1113 """Change or get currently active patient.
1114
1115 patient:
1116 * None: get currently active patient
1117 * -1: unset currently active patient
1118 * cPatient instance: set active patient if possible
1119 """
1120
1121 try:
1122 tmp = self.patient
1123 except AttributeError:
1124 self.patient = gmNull.cNull()
1125 self.__register_interests()
1126
1127
1128
1129 self.__lock_depth = 0
1130
1131 self.__pre_selection_callbacks = []
1132
1133
1134 if patient is None:
1135 return None
1136
1137
1138 if self.locked:
1139 _log.error('patient [%s] is locked, cannot change to [%s]' % (self.patient['pk_identity'], patient))
1140 return None
1141
1142
1143 if patient == -1:
1144 _log.debug('explicitly unsetting current patient')
1145 if not self.__run_pre_selection_callbacks():
1146 _log.debug('not unsetting current patient')
1147 return None
1148 self.__send_pre_selection_notification()
1149 self.patient.cleanup()
1150 self.patient = gmNull.cNull()
1151 self.__send_selection_notification()
1152 return None
1153
1154
1155 if not isinstance(patient, cPatient):
1156 _log.error('cannot set active patient to [%s], must be either None, -1 or cPatient instance' % str(patient))
1157 raise TypeError, 'gmPerson.gmCurrentPatient.__init__(): <patient> must be None, -1 or cPatient instance but is: %s' % str(patient)
1158
1159
1160 if (self.patient['pk_identity'] == patient['pk_identity']) and not forced_reload:
1161 return None
1162
1163
1164 _log.debug('patient change [%s] -> [%s] requested', self.patient['pk_identity'], patient['pk_identity'])
1165
1166
1167 if not self.__run_pre_selection_callbacks():
1168 _log.debug('not changing current patient')
1169 return None
1170 self.__send_pre_selection_notification()
1171 self.patient.cleanup()
1172 self.patient = patient
1173 self.patient.get_emr()
1174 self.__send_selection_notification()
1175
1176 return None
1177
1181
1185
1186
1187
1189 if not callable(callback):
1190 raise TypeError(u'callback [%s] not callable' % callback)
1191
1192 self.__pre_selection_callbacks.append(callback)
1193
1196
1198 raise AttributeError(u'invalid to set <connected> state')
1199
1200 connected = property(_get_connected, _set_connected)
1201
1203 return (self.__lock_depth > 0)
1204
1206 if locked:
1207 self.__lock_depth = self.__lock_depth + 1
1208 gmDispatcher.send(signal='patient_locked')
1209 else:
1210 if self.__lock_depth == 0:
1211 _log.error('lock/unlock imbalance, trying to refcount lock depth below 0')
1212 return
1213 else:
1214 self.__lock_depth = self.__lock_depth - 1
1215 gmDispatcher.send(signal='patient_unlocked')
1216
1217 locked = property(_get_locked, _set_locked)
1218
1220 _log.info('forced patient unlock at lock depth [%s]' % self.__lock_depth)
1221 self.__lock_depth = 0
1222 gmDispatcher.send(signal='patient_unlocked')
1223
1224
1225
1227 if isinstance(self.patient, gmNull.cNull):
1228 return True
1229
1230 for call_back in self.__pre_selection_callbacks:
1231 try:
1232 successful = call_back()
1233 except:
1234 _log.exception('callback [%s] failed', call_back)
1235 print "*** pre-selection callback failed ***"
1236 print type(call_back)
1237 print call_back
1238 return False
1239
1240 if not successful:
1241 _log.debug('callback [%s] returned False', call_back)
1242 return False
1243
1244 return True
1245
1247 """Sends signal when another patient is about to become active.
1248
1249 This does NOT wait for signal handlers to complete.
1250 """
1251 kwargs = {
1252 'signal': u'pre_patient_selection',
1253 'sender': id(self.__class__),
1254 'pk_identity': self.patient['pk_identity']
1255 }
1256 gmDispatcher.send(**kwargs)
1257
1259 """Sends signal when another patient has actually been made active."""
1260 kwargs = {
1261 'signal': u'post_patient_selection',
1262 'sender': id(self.__class__),
1263 'pk_identity': self.patient['pk_identity']
1264 }
1265 gmDispatcher.send(**kwargs)
1266
1267
1268
1270 if attribute == 'patient':
1271 raise AttributeError
1272 if not isinstance(self.patient, gmNull.cNull):
1273 return getattr(self.patient, attribute)
1274
1275
1276
1278 """Return any attribute if known how to retrieve it by proxy.
1279 """
1280 return self.patient[attribute]
1281
1284
1285
1286
1289 gmMatchProvider.cMatchProvider_SQL2.__init__(
1290 self,
1291 queries = [
1292 u"""select
1293 pk_staff,
1294 short_alias || ' (' || coalesce(title, '') || firstnames || ' ' || lastnames || ')',
1295 1
1296 from dem.v_staff
1297 where
1298 is_active and (
1299 short_alias %(fragment_condition)s or
1300 firstnames %(fragment_condition)s or
1301 lastnames %(fragment_condition)s or
1302 db_user %(fragment_condition)s
1303 )"""
1304 ]
1305 )
1306 self.setThresholds(1, 2, 3)
1307
1308
1309
1310 -def create_name(pk_person, firstnames, lastnames, active=False):
1311 queries = [{
1312 'cmd': u"select dem.add_name(%s, %s, %s, %s)",
1313 'args': [pk_person, firstnames, lastnames, active]
1314 }]
1315 rows, idx = gmPG2.run_rw_queries(queries=queries, return_data=True)
1316 name = cPersonName(aPK_obj = rows[0][0])
1317 return name
1318
1319 -def create_identity(gender=None, dob=None, lastnames=None, firstnames=None):
1320
1321 cmd1 = u"""insert into dem.identity (gender, dob) values (%s, %s)"""
1322
1323 cmd2 = u"""
1324 insert into dem.names (
1325 id_identity, lastnames, firstnames
1326 ) values (
1327 currval('dem.identity_pk_seq'), coalesce(%s, 'xxxDEFAULTxxx'), coalesce(%s, 'xxxDEFAULTxxx')
1328 )"""
1329
1330 rows, idx = gmPG2.run_rw_queries (
1331 queries = [
1332 {'cmd': cmd1, 'args': [gender, dob]},
1333 {'cmd': cmd2, 'args': [lastnames, firstnames]},
1334 {'cmd': u"select currval('dem.identity_pk_seq')"}
1335 ],
1336 return_data = True
1337 )
1338 return cIdentity(aPK_obj=rows[0][0])
1339
1341 cmd1 = u"insert into dem.identity(gender) values('xxxDEFAULTxxx')"
1342 cmd2 = u"select currval('dem.identity_pk_seq')"
1343
1344 rows, idx = gmPG2.run_rw_queries (
1345 queries = [
1346 {'cmd': cmd1},
1347 {'cmd': cmd2}
1348 ],
1349 return_data = True
1350 )
1351 return gmDemographicRecord.cIdentity(aPK_obj = rows[0][0])
1352
1379
1380
1381
1392
1393 map_gender2mf = {
1394 'm': u'm',
1395 'f': u'f',
1396 'tf': u'f',
1397 'tm': u'm',
1398 'h': u'mf'
1399 }
1400
1401
1402 map_gender2symbol = {
1403 'm': u'\u2642',
1404 'f': u'\u2640',
1405 'tf': u'\u26A5\u2640',
1406 'tm': u'\u26A5\u2642',
1407 'h': u'\u26A5'
1408
1409
1410
1411 }
1412
1433
1435 """Try getting the gender for the given first name."""
1436
1437 if firstnames is None:
1438 return None
1439
1440 rows, idx = gmPG2.run_ro_queries(queries = [{
1441 'cmd': u"select gender from dem.name_gender_map where name ilike %(fn)s limit 1",
1442 'args': {'fn': firstnames}
1443 }])
1444
1445 if len(rows) == 0:
1446 return None
1447
1448 return rows[0][0]
1449
1451 if active_only:
1452 cmd = u"select * from dem.v_staff where is_active order by can_login desc, short_alias asc"
1453 else:
1454 cmd = u"select * from dem.v_staff order by can_login desc, is_active desc, short_alias asc"
1455 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True)
1456 staff_list = []
1457 for row in rows:
1458 obj_row = {
1459 'idx': idx,
1460 'data': row,
1461 'pk_field': 'pk_staff'
1462 }
1463 staff_list.append(cStaff(row=obj_row))
1464 return staff_list
1465
1467 return [ cIdentity(aPK_obj = pk) for pk in pks ]
1468
1472
1476
1477
1478
1479 if __name__ == '__main__':
1480
1481 if len(sys.argv) == 1:
1482 sys.exit()
1483
1484 if sys.argv[1] != 'test':
1485 sys.exit()
1486
1487 import datetime
1488
1489 gmI18N.activate_locale()
1490 gmI18N.install_domain()
1491 gmDateTime.init()
1492
1493
1514
1516 dto = cDTO_person()
1517 dto.firstnames = 'Sepp'
1518 dto.lastnames = 'Herberger'
1519 dto.gender = 'male'
1520 dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone)
1521 print dto
1522
1523 print dto['firstnames']
1524 print dto['lastnames']
1525 print dto['gender']
1526 print dto['dob']
1527
1528 for key in dto.keys():
1529 print key
1530
1536
1549
1551
1552 print '\n\nCreating identity...'
1553 new_identity = create_identity(gender='m', dob='2005-01-01', lastnames='test lastnames', firstnames='test firstnames')
1554 print 'Identity created: %s' % new_identity
1555
1556 print '\nSetting title and gender...'
1557 new_identity['title'] = 'test title';
1558 new_identity['gender'] = 'f';
1559 new_identity.save_payload()
1560 print 'Refetching identity from db: %s' % cIdentity(aPK_obj=new_identity['pk_identity'])
1561
1562 print '\nGetting all names...'
1563 for a_name in new_identity.get_names():
1564 print a_name
1565 print 'Active name: %s' % (new_identity.get_active_name())
1566 print 'Setting nickname...'
1567 new_identity.set_nickname(nickname='test nickname')
1568 print 'Refetching all names...'
1569 for a_name in new_identity.get_names():
1570 print a_name
1571 print 'Active name: %s' % (new_identity.get_active_name())
1572
1573 print '\nIdentity occupations: %s' % new_identity['occupations']
1574 print 'Creating identity occupation...'
1575 new_identity.link_occupation('test occupation')
1576 print 'Identity occupations: %s' % new_identity['occupations']
1577
1578 print '\nIdentity addresses: %s' % new_identity.get_addresses()
1579 print 'Creating identity address...'
1580
1581 new_identity.link_address (
1582 number = 'test 1234',
1583 street = 'test street',
1584 postcode = 'test postcode',
1585 urb = 'test urb',
1586 state = 'SN',
1587 country = 'DE'
1588 )
1589 print 'Identity addresses: %s' % new_identity.get_addresses()
1590
1591 print '\nIdentity communications: %s' % new_identity.get_comm_channels()
1592 print 'Creating identity communication...'
1593 new_identity.link_comm_channel('homephone', '1234566')
1594 print 'Identity communications: %s' % new_identity.get_comm_channels()
1595
1601
1602
1603
1604
1605
1606
1607 test_current_provider()
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621