| Home | Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf8 -*-
2 """GNUmed patient objects.
3
4 This is a patient object intended to let a useful client-side
5 API crystallize from actual use in true XP fashion.
6 """
7 #============================================================
8 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
9 __license__ = "GPL"
10
11 # std lib
12 import sys
13 import os.path
14 import time
15 import re as regex
16 import datetime as pyDT
17 import codecs
18 import threading
19 import logging
20
21
22 # GNUmed
23 if __name__ == '__main__':
24 sys.path.insert(0, '../../')
25 from Gnumed.pycommon import gmExceptions
26 from Gnumed.pycommon import gmDispatcher
27 from Gnumed.pycommon import gmBorg
28 from Gnumed.pycommon import gmI18N
29 from Gnumed.pycommon import gmNull
30 from Gnumed.pycommon import gmBusinessDBObject
31 from Gnumed.pycommon import gmTools
32 from Gnumed.pycommon import gmPG2
33 from Gnumed.pycommon import gmDateTime
34 from Gnumed.pycommon import gmMatchProvider
35 from Gnumed.pycommon import gmLog2
36 from Gnumed.pycommon import gmHooks
37
38 from Gnumed.business import gmDemographicRecord
39 from Gnumed.business import gmClinicalRecord
40 from Gnumed.business import gmXdtMappings
41 from Gnumed.business import gmProviderInbox
42 from Gnumed.business.gmDocuments import cDocumentFolder
43
44
45 _log = logging.getLogger('gm.person')
46
47 __gender_list = None
48 __gender_idx = None
49
50 __gender2salutation_map = None
51 __gender2string_map = None
52
53
54 #============================================================
56 cmd = u'SELECT COUNT(1) FROM dem.lnk_identity2ext_id WHERE fk_origin = %(issuer)s AND external_id = %(val)s'
57 args = {'issuer': pk_issuer, 'val': value}
58 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
59 return rows[0][0]
60
61 #============================================================
63 args = {
64 'last': lastnames,
65 'dob': dob
66 }
67 where_parts = [
68 u"lastnames = %(last)s",
69 u"dem.date_trunc_utc('day', dob) = dem.date_trunc_utc('day', %(dob)s)"
70 ]
71 if firstnames is not None:
72 if firstnames.strip() != u'':
73 #where_parts.append(u"position(%(first)s in firstnames) = 1")
74 where_parts.append(u"firstnames ~* %(first)s")
75 args['first'] = u'\\m' + firstnames
76 cmd = u"""SELECT COUNT(1) FROM dem.v_basic_person WHERE %s""" % u' AND '.join(where_parts)
77 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
78 return rows[0][0]
79
80 #============================================================
81 # FIXME: make this work as a mapping type, too
83
89 #--------------------------------------------------------
90 # external API
91 #--------------------------------------------------------
94 #--------------------------------------------------------
97 #--------------------------------------------------------
99 """Generate generic queries.
100
101 - not locale dependant
102 - data -> firstnames, lastnames, dob, gender
103
104 shall we mogrify name parts ? probably not as external
105 sources should know what they do
106
107 finds by inactive name, too, but then shows
108 the corresponding active name ;-)
109
110 Returns list of matching identities (may be empty)
111 or None if it was told to create an identity but couldn't.
112 """
113 where_snippets = []
114 args = {}
115
116 where_snippets.append(u'firstnames = %(first)s')
117 args['first'] = self.firstnames
118
119 where_snippets.append(u'lastnames = %(last)s')
120 args['last'] = self.lastnames
121
122 if self.dob is not None:
123 where_snippets.append(u"dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %(dob)s)")
124 args['dob'] = self.dob.replace(hour = 23, minute = 59, second = 59)
125
126 if self.gender is not None:
127 where_snippets.append('gender = %(sex)s')
128 args['sex'] = self.gender
129
130 cmd = u"""
131 SELECT *, '%s' AS match_type
132 FROM dem.v_basic_person
133 WHERE
134 pk_identity IN (
135 SELECT pk_identity FROM dem.v_person_names WHERE %s
136 )
137 ORDER BY lastnames, firstnames, dob""" % (
138 _('external patient source (name, gender, date of birth)'),
139 ' AND '.join(where_snippets)
140 )
141
142 try:
143 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx=True)
144 except:
145 _log.error(u'cannot get candidate identities for dto "%s"' % self)
146 _log.exception('query %s' % cmd)
147 rows = []
148
149 if len(rows) == 0:
150 _log.debug('no candidate identity matches found')
151 if not can_create:
152 return []
153 ident = self.import_into_database()
154 if ident is None:
155 return None
156 identities = [ident]
157 else:
158 identities = [ cIdentity(row = {'pk_field': 'pk_identity', 'data': row, 'idx': idx}) for row in rows ]
159
160 return identities
161 #--------------------------------------------------------
163 """Imports self into the database."""
164
165 self.identity = create_identity (
166 firstnames = self.firstnames,
167 lastnames = self.lastnames,
168 gender = self.gender,
169 dob = self.dob
170 )
171
172 if self.identity is None:
173 return None
174
175 for ext_id in self.external_ids:
176 try:
177 self.identity.add_external_id (
178 type_name = ext_id['name'],
179 value = ext_id['value'],
180 issuer = ext_id['issuer'],
181 comment = ext_id['comment']
182 )
183 except StandardError:
184 _log.exception('cannot import <external ID> from external data source')
185 _log.log_stack_trace()
186
187 for comm in self.comm_channels:
188 try:
189 self.identity.link_comm_channel (
190 comm_medium = comm['channel'],
191 url = comm['url']
192 )
193 except StandardError:
194 _log.exception('cannot import <comm channel> from external data source')
195 _log.log_stack_trace()
196
197 for adr in self.addresses:
198 try:
199 self.identity.link_address (
200 number = adr['number'],
201 street = adr['street'],
202 postcode = adr['zip'],
203 urb = adr['urb'],
204 state = adr['region'],
205 country = adr['country']
206 )
207 except StandardError:
208 _log.exception('cannot import <address> from external data source')
209 _log.log_stack_trace()
210
211 return self.identity
212 #--------------------------------------------------------
215 #--------------------------------------------------------
217 value = value.strip()
218 if value == u'':
219 return
220 name = name.strip()
221 if name == u'':
222 raise ValueError(_('<name> cannot be empty'))
223 issuer = issuer.strip()
224 if issuer == u'':
225 raise ValueError(_('<issuer> cannot be empty'))
226 self.external_ids.append({'name': name, 'value': value, 'issuer': issuer, 'comment': comment})
227 #--------------------------------------------------------
229 url = url.strip()
230 if url == u'':
231 return
232 channel = channel.strip()
233 if channel == u'':
234 raise ValueError(_('<channel> cannot be empty'))
235 self.comm_channels.append({'channel': channel, 'url': url})
236 #--------------------------------------------------------
237 - def remember_address(self, number=None, street=None, urb=None, region=None, zip=None, country=None):
238 number = number.strip()
239 if number == u'':
240 raise ValueError(_('<number> cannot be empty'))
241 street = street.strip()
242 if street == u'':
243 raise ValueError(_('<street> cannot be empty'))
244 urb = urb.strip()
245 if urb == u'':
246 raise ValueError(_('<urb> cannot be empty'))
247 zip = zip.strip()
248 if zip == u'':
249 raise ValueError(_('<zip> cannot be empty'))
250 country = country.strip()
251 if country == u'':
252 raise ValueError(_('<country> cannot be empty'))
253 region = region.strip()
254 if region == u'':
255 region = u'??'
256 self.addresses.append ({
257 u'number': number,
258 u'street': street,
259 u'zip': zip,
260 u'urb': urb,
261 u'region': region,
262 u'country': country
263 })
264 #--------------------------------------------------------
265 # customizing behaviour
266 #--------------------------------------------------------
268 return u'<%s @ %s: %s %s (%s) %s>' % (
269 self.__class__.__name__,
270 id(self),
271 self.firstnames,
272 self.lastnames,
273 self.gender,
274 self.dob
275 )
276 #--------------------------------------------------------
278 """Do some sanity checks on self.* access."""
279
280 if attr == 'gender':
281 glist, idx = get_gender_list()
282 for gender in glist:
283 if str(val) in [gender[0], gender[1], gender[2], gender[3]]:
284 val = gender[idx['tag']]
285 object.__setattr__(self, attr, val)
286 return
287 raise ValueError('invalid gender: [%s]' % val)
288
289 if attr == 'dob':
290 if val is not None:
291 if not isinstance(val, pyDT.datetime):
292 raise TypeError('invalid type for DOB (must be datetime.datetime): %s [%s]' % (type(val), val))
293 if val.tzinfo is None:
294 raise ValueError('datetime.datetime instance is lacking a time zone: [%s]' % val.isoformat())
295
296 object.__setattr__(self, attr, val)
297 return
298 #--------------------------------------------------------
301 #============================================================
303 _cmd_fetch_payload = u"SELECT * FROM dem.v_person_names WHERE pk_name = %s"
304 _cmds_store_payload = [
305 u"""UPDATE dem.names SET
306 active = FALSE
307 WHERE
308 %(active_name)s IS TRUE -- act only when needed and only
309 AND
310 id_identity = %(pk_identity)s -- on names of this identity
311 AND
312 active IS TRUE -- which are active
313 AND
314 id != %(pk_name)s -- but NOT *this* name
315 """,
316 u"""update dem.names set
317 active = %(active_name)s,
318 preferred = %(preferred)s,
319 comment = %(comment)s
320 where
321 id = %(pk_name)s and
322 id_identity = %(pk_identity)s and -- belt and suspenders
323 xmin = %(xmin_name)s""",
324 u"""select xmin as xmin_name from dem.names where id = %(pk_name)s"""
325 ]
326 _updatable_fields = ['active_name', 'preferred', 'comment']
327 #--------------------------------------------------------
329 if attribute == 'active_name':
330 # cannot *directly* deactivate a name, only indirectly
331 # by activating another one
332 # FIXME: should be done at DB level
333 if self._payload[self._idx['active_name']] is True:
334 return
335 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
336 #--------------------------------------------------------
338 return '%(last)s, %(title)s %(first)s%(nick)s' % {
339 'last': self._payload[self._idx['lastnames']],
340 'title': gmTools.coalesce (
341 self._payload[self._idx['title']],
342 map_gender2salutation(self._payload[self._idx['gender']])
343 ),
344 'first': self._payload[self._idx['firstnames']],
345 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u" '%s'", u'%s')
346 }
347
348 description = property(_get_description, lambda x:x)
349 #============================================================
351 _cmd_fetch_payload = u"SELECT * FROM dem.v_basic_person WHERE pk_identity = %s"
352 _cmds_store_payload = [
353 u"""UPDATE dem.identity SET
354 gender = %(gender)s,
355 dob = %(dob)s,
356 dob_is_estimated = %(dob_is_estimated)s,
357 tob = %(tob)s,
358 cob = gm.nullify_empty_string(%(cob)s),
359 title = gm.nullify_empty_string(%(title)s),
360 fk_marital_status = %(pk_marital_status)s,
361 karyotype = gm.nullify_empty_string(%(karyotype)s),
362 pupic = gm.nullify_empty_string(%(pupic)s),
363 deceased = %(deceased)s,
364 emergency_contact = gm.nullify_empty_string(%(emergency_contact)s),
365 fk_emergency_contact = %(pk_emergency_contact)s,
366 fk_primary_provider = %(pk_primary_provider)s,
367 comment = gm.nullify_empty_string(%(comment)s)
368 WHERE
369 pk = %(pk_identity)s and
370 xmin = %(xmin_identity)s
371 RETURNING
372 xmin AS xmin_identity"""
373 ]
374 _updatable_fields = [
375 "title",
376 "dob",
377 "tob",
378 "cob",
379 "gender",
380 "pk_marital_status",
381 "karyotype",
382 "pupic",
383 'deceased',
384 'emergency_contact',
385 'pk_emergency_contact',
386 'pk_primary_provider',
387 'comment',
388 'dob_is_estimated'
389 ]
390 #--------------------------------------------------------
395 ID = property(_get_ID, _set_ID)
396 #--------------------------------------------------------
398
399 if attribute == 'dob':
400 if value is not None:
401
402 if isinstance(value, pyDT.datetime):
403 if value.tzinfo is None:
404 raise ValueError('datetime.datetime instance is lacking a time zone: [%s]' % dt.isoformat())
405 else:
406 raise TypeError, '[%s]: type [%s] (%s) invalid for attribute [dob], must be datetime.datetime or None' % (self.__class__.__name__, type(value), value)
407
408 # compare DOB at seconds level
409 if self._payload[self._idx['dob']] is not None:
410 old_dob = gmDateTime.pydt_strftime (
411 self._payload[self._idx['dob']],
412 format = '%Y %m %d %H %M %S',
413 accuracy = gmDateTime.acc_seconds
414 )
415 new_dob = gmDateTime.pydt_strftime (
416 value,
417 format = '%Y %m %d %H %M %S',
418 accuracy = gmDateTime.acc_seconds
419 )
420 if new_dob == old_dob:
421 return
422
423 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
424 #--------------------------------------------------------
427 #--------------------------------------------------------
429 cmd = u"""
430 SELECT EXISTS (
431 SELECT 1
432 FROM clin.v_emr_journal
433 WHERE
434 pk_patient = %(pat)s
435 AND
436 soap_cat IS NOT NULL
437 )"""
438 args = {'pat': self._payload[self._idx['pk_identity']]}
439 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
440 return rows[0][0]
441
444
445 is_patient = property(_get_is_patient, _set_is_patient)
446 #--------------------------------------------------------
448 cmd = u"SELECT pk FROM dem.staff WHERE fk_identity = %(pk)s"
449 args = {'pk': self._payload[self._idx['pk_identity']]}
450 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
451 if len(rows) == 0:
452 return None
453 return rows[0][0]
454
455 staff_id = property(_get_staff_id, lambda x:x)
456 #--------------------------------------------------------
457 # identity API
458 #--------------------------------------------------------
460 return map_gender2symbol[self._payload[self._idx['gender']]]
461
462 gender_symbol = property(_get_gender_symbol, lambda x:x)
463 #--------------------------------------------------------
465 return map_gender2string(gender = self._payload[self._idx['gender']])
466
467 gender_string = property(_get_gender_string, lambda x:x)
468 #--------------------------------------------------------
470 names = self.get_names(active_only = True)
471 if len(names) == 0:
472 _log.error('cannot retrieve active name for patient [%s]', self._payload[self._idx['pk_identity']])
473 return None
474 return names[0]
475
476 active_name = property(get_active_name, lambda x:x)
477 #--------------------------------------------------------
479
480 args = {'pk_pat': self._payload[self._idx['pk_identity']]}
481 where_parts = [u'pk_identity = %(pk_pat)s']
482 if active_only:
483 where_parts.append(u'active_name is True')
484 if exclude_active:
485 where_parts.append(u'active_name is False')
486 cmd = u"""
487 SELECT *
488 FROM dem.v_person_names
489 WHERE %s
490 ORDER BY active_name DESC, lastnames, firstnames
491 """ % u' AND '.join(where_parts)
492 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
493
494 if len(rows) == 0:
495 # no names registered for patient
496 return []
497
498 names = [ cPersonName(row = {'idx': idx, 'data': r, 'pk_field': 'pk_name'}) for r in rows ]
499 return names
500 #--------------------------------------------------------
502 return _(u'%(last)s,%(title)s %(first)s%(nick)s (%(sex)s)') % {
503 'last': self._payload[self._idx['lastnames']],
504 'title': gmTools.coalesce(self._payload[self._idx['title']], u'', u' %s'),
505 'first': self._payload[self._idx['firstnames']],
506 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u" '%s'"),
507 'sex': self.gender_symbol
508 }
509 #--------------------------------------------------------
511 return _(u'%(last)s,%(title)s %(first)s%(nick)s') % {
512 'last': self._payload[self._idx['lastnames']],
513 'title': gmTools.coalesce(self._payload[self._idx['title']], u'', u' %s'),
514 'first': self._payload[self._idx['firstnames']],
515 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], u'', u" '%s'")
516 }
517 #--------------------------------------------------------
519 """Add a name.
520
521 @param firstnames The first names.
522 @param lastnames The last names.
523 @param active When True, the new name will become the active one (hence setting other names to inactive)
524 @type active A types.BooleanType instance
525 """
526 name = create_name(self.ID, firstnames, lastnames, active)
527 if active:
528 self.refetch_payload()
529 return name
530 #--------------------------------------------------------
532 cmd = u"delete from dem.names where id = %(name)s and id_identity = %(pat)s"
533 args = {'name': name['pk_name'], 'pat': self.ID}
534 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
535 # can't have been the active name as that would raise an
536 # exception (since no active name would be left) so no
537 # data refetch needed
538 #--------------------------------------------------------
540 """
541 Set the nickname. Setting the nickname only makes sense for the currently
542 active name.
543 @param nickname The preferred/nick/warrior name to set.
544 """
545 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': u"select dem.set_nickname(%s, %s)", 'args': [self.ID, nickname]}])
546 self.refetch_payload()
547 return True
548 #--------------------------------------------------------
559
560 tags = property(get_tags, lambda x:x)
561 #--------------------------------------------------------
563 args = {
564 u'tag': tag,
565 u'identity': self.ID
566 }
567
568 # already exists ?
569 cmd = u"SELECT pk FROM dem.identity_tag WHERE fk_tag = %(tag)s AND fk_identity = %(identity)s"
570 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
571 if len(rows) > 0:
572 return gmDemographicRecord.cIdentityTag(aPK_obj = rows[0]['pk'])
573
574 # no, add
575 cmd = u"""
576 INSERT INTO dem.identity_tag (
577 fk_tag,
578 fk_identity
579 ) VALUES (
580 %(tag)s,
581 %(identity)s
582 )
583 RETURNING pk
584 """
585 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
586 return gmDemographicRecord.cIdentityTag(aPK_obj = rows[0]['pk'])
587 #--------------------------------------------------------
589 cmd = u"DELETE FROM dem.identity_tag WHERE pk = %(pk)s"
590 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': tag}}])
591 #--------------------------------------------------------
592 # external ID API
593 #
594 # since external IDs are not treated as first class
595 # citizens (classes in their own right, that is), we
596 # handle them *entirely* within cIdentity, also they
597 # only make sense with one single person (like names)
598 # and are not reused (like addresses), so they are
599 # truly added/deleted, not just linked/unlinked
600 #--------------------------------------------------------
601 - def add_external_id(self, type_name=None, value=None, issuer=None, comment=None, pk_type=None):
602 """Adds an external ID to the patient.
603
604 creates ID type if necessary
605 """
606
607 # check for existing ID
608 if pk_type is not None:
609 cmd = u"""
610 select * from dem.v_external_ids4identity where
611 pk_identity = %(pat)s and
612 pk_type = %(pk_type)s and
613 value = %(val)s"""
614 else:
615 # by type/value/issuer
616 if issuer is None:
617 cmd = u"""
618 select * from dem.v_external_ids4identity where
619 pk_identity = %(pat)s and
620 name = %(name)s and
621 value = %(val)s"""
622 else:
623 cmd = u"""
624 select * from dem.v_external_ids4identity where
625 pk_identity = %(pat)s and
626 name = %(name)s and
627 value = %(val)s and
628 issuer = %(issuer)s"""
629 args = {
630 'pat': self.ID,
631 'name': type_name,
632 'val': value,
633 'issuer': issuer,
634 'pk_type': pk_type
635 }
636 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
637
638 # create new ID if not found
639 if len(rows) == 0:
640
641 args = {
642 'pat': self.ID,
643 'val': value,
644 'type_name': type_name,
645 'pk_type': pk_type,
646 'issuer': issuer,
647 'comment': comment
648 }
649
650 if pk_type is None:
651 cmd = u"""insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values (
652 %(val)s,
653 (select dem.add_external_id_type(%(type_name)s, %(issuer)s)),
654 %(comment)s,
655 %(pat)s
656 )"""
657 else:
658 cmd = u"""insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values (
659 %(val)s,
660 %(pk_type)s,
661 %(comment)s,
662 %(pat)s
663 )"""
664
665 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
666
667 # or update comment of existing ID
668 else:
669 row = rows[0]
670 if comment is not None:
671 # comment not already there ?
672 if gmTools.coalesce(row['comment'], '').find(comment.strip()) == -1:
673 comment = '%s%s' % (gmTools.coalesce(row['comment'], '', '%s // '), comment.strip)
674 cmd = u"update dem.lnk_identity2ext_id set comment = %(comment)s where id=%(pk)s"
675 args = {'comment': comment, 'pk': row['pk_id']}
676 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
677 #--------------------------------------------------------
679 """Edits an existing external ID.
680
681 Creates ID type if necessary.
682 """
683 cmd = u"""
684 UPDATE dem.lnk_identity2ext_id SET
685 fk_origin = (SELECT dem.add_external_id_type(%(type)s, %(issuer)s)),
686 external_id = %(value)s,
687 comment = gm.nullify_empty_string(%(comment)s)
688 WHERE
689 id = %(pk)s
690 """
691 args = {'pk': pk_id, 'value': value, 'type': type, 'issuer': issuer, 'comment': comment}
692 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
693 #--------------------------------------------------------
695 where_parts = ['pk_identity = %(pat)s']
696 args = {'pat': self.ID}
697
698 if id_type is not None:
699 where_parts.append(u'name = %(name)s')
700 args['name'] = id_type.strip()
701
702 if issuer is not None:
703 where_parts.append(u'issuer = %(issuer)s')
704 args['issuer'] = issuer.strip()
705
706 cmd = u"SELECT * FROM dem.v_external_ids4identity WHERE %s" % ' AND '.join(where_parts)
707 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
708
709 return rows
710
711 external_ids = property(get_external_ids, lambda x:x)
712 #--------------------------------------------------------
714 cmd = u"""
715 delete from dem.lnk_identity2ext_id
716 where id_identity = %(pat)s and id = %(pk)s"""
717 args = {'pat': self.ID, 'pk': pk_ext_id}
718 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
719 #--------------------------------------------------------
721 """Merge another identity into this one.
722
723 Keep this one. Delete other one."""
724
725 if other_identity.ID == self.ID:
726 return True, None
727
728 curr_pat = gmCurrentPatient()
729 if curr_pat.connected:
730 if other_identity.ID == curr_pat.ID:
731 return False, _('Cannot merge active patient into another patient.')
732
733 queries = []
734 args = {'pat2del': other_identity.ID, 'pat2keep': self.ID}
735
736 # merge allergy state
737 queries.append ({
738 'cmd': u"""
739 UPDATE clin.allergy_state SET
740 has_allergy = greatest (
741 (SELECT has_allergy FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2del)s),
742 (SELECT has_allergy FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2keep)s)
743 )
744 WHERE
745 pk = (SELECT pk_allergy_state FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2keep)s)
746 """,
747 'args': args
748 })
749 # delete old allergy state
750 queries.append ({
751 'cmd': u'delete from clin.allergy_state where pk = (select pk_allergy_state from clin.v_pat_allergy_state where pk_patient = %(pat2del)s)',
752 'args': args
753 })
754
755 # transfer names
756 # 1) disambiguate names in old pat
757 queries.append ({
758 'cmd': u"""
759 UPDATE dem.names d_n1 SET
760 lastnames = lastnames || ' (%s)'
761 WHERE
762 d_n1.id_identity = %%(pat2del)s
763 AND
764 EXISTS (
765 SELECT 1 FROM dem.names d_n2
766 WHERE
767 d_n2.id_identity = %%(pat2keep)s
768 AND
769 d_n2.lastnames = d_n1.lastnames
770 AND
771 d_n2.firstnames = d_n1.firstnames
772 )""" % _('assimilated'),
773 'args': args
774 })
775 # 2) move inactive ones (but beware of dupes)
776 queries.append ({
777 'cmd': u"""
778 UPDATE dem.names SET
779 id_identity = %(pat2keep)s
780 WHERE id_identity = %(pat2del)s AND active IS false""",
781 'args': args
782 })
783 # 3) copy active ones
784 queries.append ({
785 'cmd': u"""
786 INSERT INTO dem.names (
787 id_identity, active, lastnames, firstnames, preferred, comment
788 ) SELECT
789 %(pat2keep)s, false, lastnames, firstnames, preferred, comment
790 FROM dem.names d_n
791 WHERE d_n.id_identity = %(pat2del)s AND d_n.active IS true""",
792 'args': args
793 })
794
795 # find FKs pointing to identity
796 FKs = gmPG2.get_foreign_keys2column (
797 schema = u'dem',
798 table = u'identity',
799 column = u'pk'
800 )
801
802 # disambiguate potential dupes
803 # - same-url comm channels
804 queries.append ({
805 'cmd': u"""
806 UPDATE dem.lnk_identity2comm
807 SET url = url || ' (%s %s)'
808 WHERE
809 fk_identity = %%(pat2del)s
810 AND
811 EXISTS (
812 SELECT 1 FROM dem.lnk_identity2comm d_li2c
813 WHERE d_li2c.fk_identity = %%(pat2keep)s AND d_li2c.url = url
814 )
815 """ % (_('merged'), gmDateTime.pydt_strftime()),
816 'args': args
817 })
818 # - same-value external IDs
819 queries.append ({
820 'cmd': u"""
821 UPDATE dem.lnk_identity2ext_id
822 SET external_id = external_id || ' (%s %s)'
823 WHERE
824 id_identity = %%(pat2del)s
825 AND
826 EXISTS (
827 SELECT 1 FROM dem.lnk_identity2ext_id d_li2e
828 WHERE
829 d_li2e.id_identity = %%(pat2keep)s
830 AND
831 d_li2e.external_id = external_id
832 AND
833 d_li2e.fk_origin = fk_origin
834 )
835 """ % (_('merged'), gmDateTime.pydt_strftime()),
836 'args': args
837 })
838 # - same addresses
839 queries.append ({
840 'cmd': u"""
841 DELETE FROM dem.lnk_person_org_address
842 WHERE
843 id_identity = %(pat2del)s
844 AND
845 id_address IN (
846 SELECT id_address FROM dem.lnk_person_org_address d_lpoa
847 WHERE d_lpoa.id_identity = %(pat2keep)s
848 )
849 """,
850 'args': args
851 })
852
853 # generate UPDATEs
854 cmd_template = u'UPDATE %s SET %s = %%(pat2keep)s WHERE %s = %%(pat2del)s'
855 for FK in FKs:
856 if FK['referencing_table'] == u'dem.names':
857 continue
858 queries.append ({
859 'cmd': cmd_template % (FK['referencing_table'], FK['referencing_column'], FK['referencing_column']),
860 'args': args
861 })
862
863 # remove old identity entry
864 queries.append ({
865 'cmd': u'delete from dem.identity where pk = %(pat2del)s',
866 'args': args
867 })
868
869 _log.warning('identity [%s] is about to assimilate identity [%s]', self.ID, other_identity.ID)
870
871 gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, end_tx = True)
872
873 self.add_external_id (
874 type_name = u'merged GNUmed identity primary key',
875 value = u'GNUmed::pk::%s' % other_identity.ID,
876 issuer = u'GNUmed'
877 )
878
879 return True, None
880 #--------------------------------------------------------
881 #--------------------------------------------------------
883 cmd = u"""
884 insert into clin.waiting_list (fk_patient, urgency, comment, area, list_position)
885 values (
886 %(pat)s,
887 %(urg)s,
888 %(cmt)s,
889 %(area)s,
890 (select coalesce((max(list_position) + 1), 1) from clin.waiting_list)
891 )"""
892 args = {'pat': self.ID, 'urg': urgency, 'cmt': comment, 'area': zone}
893 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], verbose = True)
894 #--------------------------------------------------------
896 cmd = u"""SELECT * FROM clin.v_waiting_list WHERE pk_identity = %(pat)s"""
897 args = {'pat': self.ID}
898 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
899 return rows
900
901 waiting_list_entries = property(get_waiting_list_entry, lambda x:x)
902 #--------------------------------------------------------
906
907 export_tray = property(_get_export_tray, lambda x:x)
908 #--------------------------------------------------------
910
911 template = u'%s%s%s\r\n'
912
913 file = codecs.open (
914 filename = filename,
915 mode = 'wb',
916 encoding = encoding,
917 errors = 'strict'
918 )
919
920 file.write(template % (u'013', u'8000', u'6301'))
921 file.write(template % (u'013', u'9218', u'2.10'))
922 if external_id_type is None:
923 file.write(template % (u'%03d' % (9 + len(str(self.ID))), u'3000', self.ID))
924 else:
925 ext_ids = self.get_external_ids(id_type = external_id_type)
926 if len(ext_ids) > 0:
927 file.write(template % (u'%03d' % (9 + len(ext_ids[0]['value'])), u'3000', ext_ids[0]['value']))
928 file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['lastnames']])), u'3101', self._payload[self._idx['lastnames']]))
929 file.write(template % (u'%03d' % (9 + len(self._payload[self._idx['firstnames']])), u'3102', self._payload[self._idx['firstnames']]))
930 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')))
931 file.write(template % (u'010', u'3110', gmXdtMappings.map_gender_gm2xdt[self._payload[self._idx['gender']]]))
932 file.write(template % (u'025', u'6330', 'GNUmed::9206::encoding'))
933 file.write(template % (u'%03d' % (9 + len(encoding)), u'6331', encoding))
934 if external_id_type is None:
935 file.write(template % (u'029', u'6332', u'GNUmed::3000::source'))
936 file.write(template % (u'017', u'6333', u'internal'))
937 else:
938 if len(ext_ids) > 0:
939 file.write(template % (u'029', u'6332', u'GNUmed::3000::source'))
940 file.write(template % (u'%03d' % (9 + len(external_id_type)), u'6333', external_id_type))
941
942 file.close()
943 #--------------------------------------------------------
944 # occupations API
945 #--------------------------------------------------------
948 #--------------------------------------------------------
950 """Link an occupation with a patient, creating the occupation if it does not exists.
951
952 @param occupation The name of the occupation to link the patient to.
953 """
954 if (activities is None) and (occupation is None):
955 return True
956
957 occupation = occupation.strip()
958 if len(occupation) == 0:
959 return True
960
961 if activities is not None:
962 activities = activities.strip()
963
964 args = {'act': activities, 'pat_id': self.pk_obj, 'job': occupation}
965
966 cmd = u"select activities from dem.v_person_jobs where pk_identity = %(pat_id)s and l10n_occupation = _(%(job)s)"
967 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
968
969 queries = []
970 if len(rows) == 0:
971 queries.append ({
972 'cmd': u"INSERT INTO dem.lnk_job2person (fk_identity, fk_occupation, activities) VALUES (%(pat_id)s, dem.create_occupation(%(job)s), %(act)s)",
973 'args': args
974 })
975 else:
976 if rows[0]['activities'] != activities:
977 queries.append ({
978 '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))",
979 'args': args
980 })
981
982 rows, idx = gmPG2.run_rw_queries(queries = queries)
983
984 return True
985 #--------------------------------------------------------
987 if occupation is None:
988 return True
989 occupation = occupation.strip()
990 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))"
991 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj, 'job': occupation}}])
992 return True
993 #--------------------------------------------------------
994 # comms API
995 #--------------------------------------------------------
997 cmd = u"select * from dem.v_person_comms where pk_identity = %s"
998 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx = True)
999
1000 filtered = rows
1001
1002 if comm_medium is not None:
1003 filtered = []
1004 for row in rows:
1005 if row['comm_type'] == comm_medium:
1006 filtered.append(row)
1007
1008 return [ gmDemographicRecord.cCommChannel(row = {
1009 'pk_field': 'pk_lnk_identity2comm',
1010 'data': r,
1011 'idx': idx
1012 }) for r in filtered
1013 ]
1014 #--------------------------------------------------------
1015 - def link_comm_channel(self, comm_medium=None, url=None, is_confidential=False, pk_channel_type=None):
1016 """Link a communication medium with a patient.
1017
1018 @param comm_medium The name of the communication medium.
1019 @param url The communication resource locator.
1020 @type url A types.StringType instance.
1021 @param is_confidential Wether the data must be treated as confidential.
1022 @type is_confidential A types.BooleanType instance.
1023 """
1024 comm_channel = gmDemographicRecord.create_comm_channel (
1025 comm_medium = comm_medium,
1026 url = url,
1027 is_confidential = is_confidential,
1028 pk_channel_type = pk_channel_type,
1029 pk_identity = self.pk_obj
1030 )
1031 return comm_channel
1032 #--------------------------------------------------------
1034 gmDemographicRecord.delete_comm_channel (
1035 pk = comm_channel['pk_lnk_identity2comm'],
1036 pk_patient = self.pk_obj
1037 )
1038 #--------------------------------------------------------
1039 # contacts API
1040 #--------------------------------------------------------
1042
1043 cmd = u"SELECT * FROM dem.v_pat_addresses WHERE pk_identity = %(pat)s"
1044 args = {'pat': self.pk_obj}
1045 if address_type is not None:
1046 cmd = cmd + u" AND address_type = %(typ)s"
1047 args['typ'] = address_type
1048
1049 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1050
1051 return [
1052 gmDemographicRecord.cPatientAddress(row = {'idx': idx, 'data': r, 'pk_field': 'pk_address'})
1053 for r in rows
1054 ]
1055 #--------------------------------------------------------
1056 - def link_address(self, number=None, street=None, postcode=None, urb=None, state=None, country=None, subunit=None, suburb=None, id_type=None, address=None):
1057 """Link an address with a patient, creating the address if it does not exists.
1058
1059 @param id_type The primary key of the address type.
1060 """
1061 if address is None:
1062 address = gmDemographicRecord.create_address (
1063 country = country,
1064 state = state,
1065 urb = urb,
1066 suburb = suburb,
1067 postcode = postcode,
1068 street = street,
1069 number = number,
1070 subunit = subunit
1071 )
1072
1073 if address is None:
1074 return None
1075
1076 # already linked ?
1077 cmd = u"SELECT id_address FROM dem.lnk_person_org_address WHERE id_identity = %(pat)s AND id_address = %(adr)s"
1078 args = {'pat': self.pk_obj, 'adr': address['pk_address']}
1079 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1080
1081 # no, link to person
1082 if len(rows) == 0:
1083 args = {'id': self.pk_obj, 'adr': address['pk_address'], 'type': id_type}
1084 cmd = u"""
1085 INSERT INTO dem.lnk_person_org_address(id_identity, id_address)
1086 VALUES (%(id)s, %(adr)s)
1087 RETURNING id_address"""
1088 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
1089
1090 linked_adr = gmDemographicRecord.cPatientAddress(aPK_obj = rows[0]['id_address'])
1091
1092 # possibly change type
1093 if id_type is not None:
1094 linked_adr['pk_address_type'] = id_type
1095 linked_adr.save()
1096
1097 return linked_adr
1098 #----------------------------------------------------------------------
1100 """Remove an address from the patient.
1101
1102 The address itself stays in the database.
1103 The address can be either cAdress or cPatientAdress.
1104 """
1105 if pk_address is None:
1106 args = {'person': self.pk_obj, 'adr': address['pk_address']}
1107 else:
1108 args = {'person': self.pk_obj, 'adr': pk_address}
1109 cmd = u"DELETE FROM dem.lnk_person_org_address WHERE id_identity = %(person)s AND id_address = %(adr)s"
1110 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1111 #----------------------------------------------------------------------
1112 # relatives API
1113 #----------------------------------------------------------------------
1115 cmd = u"""
1116 select
1117 t.description,
1118 vbp.pk_identity as id,
1119 title,
1120 firstnames,
1121 lastnames,
1122 dob,
1123 cob,
1124 gender,
1125 karyotype,
1126 pupic,
1127 pk_marital_status,
1128 marital_status,
1129 xmin_identity,
1130 preferred
1131 from
1132 dem.v_basic_person vbp, dem.relation_types t, dem.lnk_person2relative l
1133 where
1134 (
1135 l.id_identity = %(pk)s and
1136 vbp.pk_identity = l.id_relative and
1137 t.id = l.id_relation_type
1138 ) or (
1139 l.id_relative = %(pk)s and
1140 vbp.pk_identity = l.id_identity and
1141 t.inverse = l.id_relation_type
1142 )"""
1143 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
1144 if len(rows) == 0:
1145 return []
1146 return [(row[0], cIdentity(row = {'data': row[1:], 'idx':idx, 'pk_field': 'pk'})) for row in rows]
1147 #--------------------------------------------------------
1149 # create new relative
1150 id_new_relative = create_dummy_identity()
1151
1152 relative = cIdentity(aPK_obj=id_new_relative)
1153 # pre-fill with data from ourselves
1154 # relative.copy_addresses(self)
1155 relative.add_name( '**?**', self.get_names()['lastnames'])
1156 # and link the two
1157 if self._ext_cache.has_key('relatives'):
1158 del self._ext_cache['relatives']
1159 cmd = u"""
1160 insert into dem.lnk_person2relative (
1161 id_identity, id_relative, id_relation_type
1162 ) values (
1163 %s, %s, (select id from dem.relation_types where description = %s)
1164 )"""
1165 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [self.ID, id_new_relative, rel_type ]}])
1166 return True
1167 #----------------------------------------------------------------------
1171 #--------------------------------------------------------
1173 if self._payload[self._idx['pk_emergency_contact']] is None:
1174 return None
1175 return cIdentity(aPK_obj = self._payload[self._idx['pk_emergency_contact']])
1176
1177 emergency_contact_in_database = property(_get_emergency_contact_from_database, lambda x:x)
1178 #----------------------------------------------------------------------
1179 # age/dob related
1180 #----------------------------------------------------------------------
1182 return gmDateTime.format_dob (
1183 self._payload[self._idx['dob']],
1184 format = format,
1185 encoding = encoding,
1186 none_string = none_string,
1187 dob_is_estimated = self._payload[self._idx['dob_is_estimated']]
1188 )
1189 #----------------------------------------------------------------------
1191 dob = self['dob']
1192
1193 if dob is None:
1194 return u'??'
1195
1196 if dob > gmDateTime.pydt_now_here():
1197 return _('invalid age: DOB in the future')
1198
1199 death = self['deceased']
1200
1201 if death is None:
1202 return u'%s%s' % (
1203 gmTools.bool2subst (
1204 self._payload[self._idx['dob_is_estimated']],
1205 gmTools.u_almost_equal_to,
1206 u''
1207 ),
1208 gmDateTime.format_apparent_age_medically (
1209 age = gmDateTime.calculate_apparent_age(start = dob)
1210 )
1211 )
1212
1213 if dob > death:
1214 return _('invalid age: DOB after death')
1215
1216 return u'%s%s%s' % (
1217 gmTools.u_latin_cross,
1218 gmTools.bool2subst (
1219 self._payload[self._idx['dob_is_estimated']],
1220 gmTools.u_almost_equal_to,
1221 u''
1222 ),
1223 gmDateTime.format_apparent_age_medically (
1224 age = gmDateTime.calculate_apparent_age (
1225 start = dob,
1226 end = self['deceased']
1227 )
1228 )
1229 )
1230 #----------------------------------------------------------------------
1232 cmd = u'select dem.dob_is_in_range(%(dob)s, %(min)s, %(max)s)'
1233 rows, idx = gmPG2.run_ro_queries (
1234 queries = [{
1235 'cmd': cmd,
1236 'args': {'dob': self['dob'], 'min': min_distance, 'max': max_distance}
1237 }]
1238 )
1239 return rows[0][0]
1240 #----------------------------------------------------------------------
1241 # practice related
1242 #----------------------------------------------------------------------
1244 cmd = u'select * from clin.v_most_recent_encounters where pk_patient=%s'
1245 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self._payload[self._idx['pk_identity']]]}])
1246 if len(rows) > 0:
1247 return rows[0]
1248 else:
1249 return None
1250 #--------------------------------------------------------
1252 return gmProviderInbox.get_inbox_messages(pk_patient = self._payload[self._idx['pk_identity']], order_by = order_by)
1253
1254 messages = property(get_messages, lambda x:x)
1255 #--------------------------------------------------------
1257 return gmProviderInbox.get_overdue_messages(pk_patient = self._payload[self._idx['pk_identity']])
1258
1259 overdue_messages = property(_get_overdue_messages, lambda x:x)
1260 #--------------------------------------------------------
1263 #--------------------------------------------------------
1265 return gmProviderInbox.get_hints_for_patient(pk_identity = self._payload[self._idx['pk_identity']])
1266
1267 dynamic_hints = property(_get_dynamic_hints, lambda x:x)
1268 #--------------------------------------------------------
1270 if self._payload[self._idx['pk_primary_provider']] is None:
1271 return None
1272 from Gnumed.business import gmStaff
1273 return gmStaff.cStaff(aPK_obj = self._payload[self._idx['pk_primary_provider']])
1274
1275 primary_provider = property(_get_primary_provider, lambda x:x)
1276 #----------------------------------------------------------------------
1277 # convenience
1278 #----------------------------------------------------------------------
1280 """Format patient demographics into patient specific path name fragment."""
1281 return (u'%s-%s%s-%s' % (
1282 self._payload[self._idx['lastnames']].replace(u' ', u'_'),
1283 self._payload[self._idx['firstnames']].replace(u' ', u'_'),
1284 gmTools.coalesce(self._payload[self._idx['preferred']], u'', template_initial = u'-(%s)').replace(u' ', u'_'),
1285 self.get_formatted_dob(format = '%Y-%m-%d', encoding = gmI18N.get_encoding())
1286 )).replace (
1287 u"'", u""
1288 ).replace (
1289 u'"', u''
1290 ).replace (
1291 u'/', u'_'
1292 ).replace (
1293 u'\\', u'_'
1294 ).replace (
1295 u'~', u''
1296 ).replace (
1297 u'|', u'_'
1298 ).replace (
1299 u'*', u''
1300 ).replace (
1301 u'\u2248', u'' # "approximately", having been added by dob_is_estimated
1302 )
1303
1304 dirname = property(get_dirname, lambda x:x)
1305 #----------------------------------------------------------------------
1309
1310 tray_dir_name = property(_get_tray_dir_name, lambda x:x)
1311 #============================================================
1312 # helper functions
1313 #------------------------------------------------------------
1314 #_spin_on_emr_access = None
1315 #
1316 #def set_emr_access_spinner(func=None):
1317 # if not callable(func):
1318 # _log.error('[%] not callable, not setting _spin_on_emr_access', func)
1319 # return False
1320 #
1321 # _log.debug('setting _spin_on_emr_access to [%s]', func)
1322 #
1323 # global _spin_on_emr_access
1324 # _spin_on_emr_access = func
1325
1326 #============================================================
1328 """Represents a person which is a patient.
1329
1330 - a specializing subclass of cIdentity turning it into a patient
1331 - its use is to cache subobjects like EMR and document folder
1332 """
1334 cIdentity.__init__(self, aPK_obj=aPK_obj, row=row)
1335 self.__db_cache = {}
1336 self.__emr_access_lock = threading.Lock()
1337 #--------------------------------------------------------
1339 """Do cleanups before dying.
1340
1341 - note that this may be called in a thread
1342 """
1343 if self.__db_cache.has_key('clinical record'):
1344 self.__db_cache['clinical record'].cleanup()
1345 if self.__db_cache.has_key('document folder'):
1346 self.__db_cache['document folder'].cleanup()
1347 cIdentity.cleanup(self)
1348 #----------------------------------------------------------
1350 if not self.__emr_access_lock.acquire(False):
1351 # maybe something slow is happening on the machine
1352 _log.debug('failed to acquire EMR access lock, sleeping for 500ms')
1353 time.sleep(0.5)
1354 if not self.__emr_access_lock.acquire(False):
1355 _log.debug('still failed to acquire EMR access lock, aborting')
1356 raise AttributeError('cannot lock access to EMR')
1357 try:
1358 self.__db_cache['clinical record']
1359 except KeyError:
1360 self.__db_cache['clinical record'] = gmClinicalRecord.cClinicalRecord(aPKey = self._payload[self._idx['pk_identity']], allow_user_interaction = allow_user_interaction)
1361 self.__emr_access_lock.release()
1362 return self.__db_cache['clinical record']
1363
1364 emr = property(get_emr, lambda x:x)
1365 #--------------------------------------------------------
1367 try:
1368 return self.__db_cache['document folder']
1369 except KeyError:
1370 pass
1371
1372 self.__db_cache['document folder'] = cDocumentFolder(aPKey = self._payload[self._idx['pk_identity']])
1373 return self.__db_cache['document folder']
1374
1375 document_folder = property(get_document_folder, lambda x:x)
1376 #============================================================
1378 """Patient Borg to hold currently active patient.
1379
1380 There may be many instances of this but they all share state.
1381 """
1383 """Change or get currently active patient.
1384
1385 patient:
1386 * None: get currently active patient
1387 * -1: unset currently active patient
1388 * cPatient instance: set active patient if possible
1389 """
1390 # make sure we do have a patient pointer
1391 try:
1392 tmp = self.patient
1393 except AttributeError:
1394 self.patient = gmNull.cNull()
1395 self.__register_interests()
1396 # set initial lock state,
1397 # this lock protects against activating another patient
1398 # when we are controlled from a remote application
1399 self.__lock_depth = 0
1400 # initialize callback state
1401 self.__pre_selection_callbacks = []
1402
1403 # user wants copy of current patient
1404 if patient is None:
1405 return None
1406
1407 # do nothing if patient is locked
1408 if self.locked:
1409 _log.error('patient [%s] is locked, cannot change to [%s]' % (self.patient['pk_identity'], patient))
1410 return None
1411
1412 # user wants to explicitly unset current patient
1413 if patient == -1:
1414 _log.debug('explicitly unsetting current patient')
1415 if not self.__run_pre_selection_callbacks():
1416 _log.debug('not unsetting current patient')
1417 return None
1418 self.__send_pre_selection_notification()
1419 self.patient.cleanup()
1420 self.patient = gmNull.cNull()
1421 self.__send_selection_notification()
1422 return None
1423
1424 # must be cPatient instance, then
1425 if not isinstance(patient, cPatient):
1426 _log.error('cannot set active patient to [%s], must be either None, -1 or cPatient instance' % str(patient))
1427 raise TypeError, 'gmPerson.gmCurrentPatient.__init__(): <patient> must be None, -1 or cPatient instance but is: %s' % str(patient)
1428
1429 # same ID, no change needed
1430 if (self.patient['pk_identity'] == patient['pk_identity']) and not forced_reload:
1431 return None
1432
1433 # user wants different patient
1434 _log.debug('patient change [%s] -> [%s] requested', self.patient['pk_identity'], patient['pk_identity'])
1435
1436 # everything seems swell
1437 if not self.__run_pre_selection_callbacks():
1438 _log.debug('not changing current patient')
1439 return None
1440 self.__send_pre_selection_notification()
1441 self.patient.cleanup()
1442 self.patient = patient
1443 self.patient.get_emr()
1444 self.__send_selection_notification()
1445
1446 return None
1447 #--------------------------------------------------------
1449 gmDispatcher.connect(signal = u'dem.identity_mod_db', receiver = self._on_identity_change)
1450 gmDispatcher.connect(signal = u'dem.names_mod_db', receiver = self._on_identity_change)
1451 #--------------------------------------------------------
1455 #--------------------------------------------------------
1456 # external API
1457 #--------------------------------------------------------
1459 if not callable(callback):
1460 raise TypeError(u'callback [%s] not callable' % callback)
1461
1462 self.__pre_selection_callbacks.append(callback)
1463 #--------------------------------------------------------
1466
1469
1470 connected = property(_get_connected, _set_connected)
1471 #--------------------------------------------------------
1474
1476 if locked:
1477 self.__lock_depth = self.__lock_depth + 1
1478 gmDispatcher.send(signal='patient_locked')
1479 else:
1480 if self.__lock_depth == 0:
1481 _log.error('lock/unlock imbalance, trying to refcount lock depth below 0')
1482 return
1483 else:
1484 self.__lock_depth = self.__lock_depth - 1
1485 gmDispatcher.send(signal='patient_unlocked')
1486
1487 locked = property(_get_locked, _set_locked)
1488 #--------------------------------------------------------
1490 _log.info('forced patient unlock at lock depth [%s]' % self.__lock_depth)
1491 self.__lock_depth = 0
1492 gmDispatcher.send(signal='patient_unlocked')
1493 #--------------------------------------------------------
1494 # patient change handling
1495 #--------------------------------------------------------
1497 if isinstance(self.patient, gmNull.cNull):
1498 return True
1499
1500 for call_back in self.__pre_selection_callbacks:
1501 try:
1502 successful = call_back()
1503 except:
1504 _log.exception('callback [%s] failed', call_back)
1505 print "*** pre-selection callback failed ***"
1506 print type(call_back)
1507 print call_back
1508 return False
1509
1510 if not successful:
1511 _log.debug('callback [%s] returned False', call_back)
1512 return False
1513
1514 return True
1515 #--------------------------------------------------------
1517 """Sends signal when another patient is about to become active.
1518
1519 This does NOT wait for signal handlers to complete.
1520 """
1521 kwargs = {
1522 'signal': u'pre_patient_selection',
1523 'sender': id(self.__class__),
1524 'pk_identity': self.patient['pk_identity']
1525 }
1526 gmDispatcher.send(**kwargs)
1527 #--------------------------------------------------------
1529 """Sends signal when another patient has actually been made active."""
1530 kwargs = {
1531 'signal': u'post_patient_selection',
1532 'sender': id(self.__class__),
1533 'pk_identity': self.patient['pk_identity']
1534 }
1535 gmDispatcher.send(**kwargs)
1536 #--------------------------------------------------------
1537 # __getattr__ handling
1538 #--------------------------------------------------------
1540 if attribute == 'patient':
1541 raise AttributeError
1542 if not isinstance(self.patient, gmNull.cNull):
1543 return getattr(self.patient, attribute)
1544 #--------------------------------------------------------
1545 # __get/setitem__ handling
1546 #--------------------------------------------------------
1548 """Return any attribute if known how to retrieve it by proxy.
1549 """
1550 return self.patient[attribute]
1551 #--------------------------------------------------------
1554 #============================================================
1555 # match providers
1556 #============================================================
1559 gmMatchProvider.cMatchProvider_SQL2.__init__(
1560 self,
1561 queries = [
1562 u"""SELECT
1563 pk_staff AS data,
1564 short_alias || ' (' || coalesce(title, '') || ' ' || firstnames || ' ' || lastnames || ')' AS list_label,
1565 short_alias || ' (' || coalesce(title, '') || ' ' || firstnames || ' ' || lastnames || ')' AS field_label
1566 FROM dem.v_staff
1567 WHERE
1568 is_active AND (
1569 short_alias %(fragment_condition)s OR
1570 firstnames %(fragment_condition)s OR
1571 lastnames %(fragment_condition)s OR
1572 db_user %(fragment_condition)s
1573 )
1574 """
1575 ]
1576 )
1577 self.setThresholds(1, 2, 3)
1578 #============================================================
1579 # convenience functions
1580 #============================================================
1582 queries = [{
1583 'cmd': u"select dem.add_name(%s, %s, %s, %s)",
1584 'args': [pk_person, firstnames, lastnames, active]
1585 }]
1586 rows, idx = gmPG2.run_rw_queries(queries=queries, return_data=True)
1587 name = cPersonName(aPK_obj = rows[0][0])
1588 return name
1589 #============================================================
1591
1592 cmd1 = u"""INSERT INTO dem.identity (gender, dob) VALUES (%s, %s)"""
1593 cmd2 = u"""
1594 INSERT INTO dem.names (
1595 id_identity, lastnames, firstnames
1596 ) VALUES (
1597 currval('dem.identity_pk_seq'), coalesce(%s, 'xxxDEFAULTxxx'), coalesce(%s, 'xxxDEFAULTxxx')
1598 ) RETURNING id_identity"""
1599 rows, idx = gmPG2.run_rw_queries (
1600 queries = [
1601 {'cmd': cmd1, 'args': [gender, dob]},
1602 {'cmd': cmd2, 'args': [lastnames, firstnames]}
1603 ],
1604 return_data = True
1605 )
1606 ident = cIdentity(aPK_obj=rows[0][0])
1607 gmHooks.run_hook_script(hook = u'post_person_creation')
1608 return ident
1609 #============================================================
1611 cmd = u"INSERT INTO dem.identity(gender) VALUES ('xxxDEFAULTxxx') RETURNING pk"
1612 rows, idx = gmPG2.run_rw_queries (
1613 queries = [{'cmd': cmd}],
1614 return_data = True
1615 )
1616 return gmDemographicRecord.cIdentity(aPK_obj = rows[0][0])
1617 #============================================================
1619 """Set active patient.
1620
1621 If patient is -1 the active patient will be UNset.
1622 """
1623 if isinstance(patient, cPatient):
1624 pat = patient
1625 elif isinstance(patient, cIdentity):
1626 pat = cPatient(aPK_obj = patient['pk_identity'])
1627 # elif isinstance(patient, cStaff):
1628 # pat = cPatient(aPK_obj=patient['pk_identity'])
1629 elif isinstance(patient, gmCurrentPatient):
1630 pat = patient.patient
1631 elif patient == -1:
1632 pat = patient
1633 else:
1634 # maybe integer ?
1635 success, pk = gmTools.input2int(initial = patient, minval = 1)
1636 if not success:
1637 raise ValueError('<patient> must be either -1, >0, or a cPatient, cIdentity or gmCurrentPatient instance, is: %s' % patient)
1638 # but also valid patient ID ?
1639 try:
1640 pat = cPatient(aPK_obj = pk)
1641 except:
1642 _log.exception('error changing active patient to [%s]' % patient)
1643 return False
1644
1645 # attempt to switch
1646 try:
1647 gmCurrentPatient(patient = pat, forced_reload = forced_reload)
1648 except:
1649 _log.exception('error changing active patient to [%s]' % patient)
1650 return False
1651
1652 return True
1653 #============================================================
1654 # gender related
1655 #------------------------------------------------------------
1657 """Retrieves the list of known genders from the database."""
1658 global __gender_idx
1659 global __gender_list
1660
1661 if __gender_list is None:
1662 cmd = u"select tag, l10n_tag, label, l10n_label, sort_weight from dem.v_gender_labels order by sort_weight desc"
1663 __gender_list, __gender_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
1664
1665 return (__gender_list, __gender_idx)
1666 #------------------------------------------------------------
1667 map_gender2mf = {
1668 'm': u'm',
1669 'f': u'f',
1670 'tf': u'f',
1671 'tm': u'm',
1672 'h': u'mf'
1673 }
1674 #------------------------------------------------------------
1675 # Maps GNUmed related i18n-aware gender specifiers to a unicode symbol.
1676 map_gender2symbol = {
1677 'm': u'\u2642',
1678 'f': u'\u2640',
1679 'tf': u'\u26A5\u2640',
1680 'tm': u'\u26A5\u2642',
1681 'h': u'\u26A5'
1682 # 'tf': u'\u2642\u2640-\u2640',
1683 # 'tm': u'\u2642\u2640-\u2642',
1684 # 'h': u'\u2642\u2640'
1685 }
1686 #------------------------------------------------------------
1688 """Maps GNUmed related i18n-aware gender specifiers to a human-readable string."""
1689
1690 global __gender2string_map
1691
1692 if __gender2string_map is None:
1693 genders, idx = get_gender_list()
1694 __gender2string_map = {
1695 'm': _('male'),
1696 'f': _('female'),
1697 'tf': u'',
1698 'tm': u'',
1699 'h': u''
1700 }
1701 for g in genders:
1702 __gender2string_map[g[idx['l10n_tag']]] = g[idx['l10n_label']]
1703 __gender2string_map[g[idx['tag']]] = g[idx['l10n_label']]
1704
1705 return __gender2string_map[gender]
1706 #------------------------------------------------------------
1708 """Maps GNUmed related i18n-aware gender specifiers to a human-readable salutation."""
1709
1710 global __gender2salutation_map
1711
1712 if __gender2salutation_map is None:
1713 genders, idx = get_gender_list()
1714 __gender2salutation_map = {
1715 'm': _('Mr'),
1716 'f': _('Mrs'),
1717 'tf': u'',
1718 'tm': u'',
1719 'h': u''
1720 }
1721 for g in genders:
1722 __gender2salutation_map[g[idx['l10n_tag']]] = __gender2salutation_map[g[idx['tag']]]
1723 __gender2salutation_map[g[idx['label']]] = __gender2salutation_map[g[idx['tag']]]
1724 __gender2salutation_map[g[idx['l10n_label']]] = __gender2salutation_map[g[idx['tag']]]
1725
1726 return __gender2salutation_map[gender]
1727 #------------------------------------------------------------
1729 """Try getting the gender for the given first name."""
1730
1731 if firstnames is None:
1732 return None
1733
1734 rows, idx = gmPG2.run_ro_queries(queries = [{
1735 'cmd': u"select gender from dem.name_gender_map where name ilike %(fn)s limit 1",
1736 'args': {'fn': firstnames}
1737 }])
1738
1739 if len(rows) == 0:
1740 return None
1741
1742 return rows[0][0]
1743 #============================================================
1745 return [ cIdentity(aPK_obj = pk) for pk in pks ]
1746 #============================================================
1748 from Gnumed.business import gmXdtObjects
1749 return gmXdtObjects.read_person_from_xdt(filename=filename, encoding=encoding, dob_format=dob_format)
1750 #============================================================
1752 from Gnumed.business import gmPracSoftAU
1753 return gmPracSoftAU.read_persons_from_pracsoft_file(filename=filename, encoding=encoding)
1754 #============================================================
1755 # main/testing
1756 #============================================================
1757 if __name__ == '__main__':
1758
1759 if len(sys.argv) == 1:
1760 sys.exit()
1761
1762 if sys.argv[1] != 'test':
1763 sys.exit()
1764
1765 import datetime
1766
1767 gmI18N.activate_locale()
1768 gmI18N.install_domain()
1769 gmDateTime.init()
1770
1771 #--------------------------------------------------------
1773
1774 ident = cIdentity(1)
1775 print "setting active patient with", ident
1776 set_active_patient(patient=ident)
1777
1778 patient = cPatient(12)
1779 print "setting active patient with", patient
1780 set_active_patient(patient=patient)
1781
1782 pat = gmCurrentPatient()
1783 print pat['dob']
1784 #pat['dob'] = 'test'
1785
1786 # staff = cStaff()
1787 # print "setting active patient with", staff
1788 # set_active_patient(patient=staff)
1789
1790 print "setting active patient with -1"
1791 set_active_patient(patient=-1)
1792 #--------------------------------------------------------
1794 dto = cDTO_person()
1795 dto.firstnames = 'Sepp'
1796 dto.lastnames = 'Herberger'
1797 dto.gender = 'male'
1798 dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone)
1799 print dto
1800
1801 print dto['firstnames']
1802 print dto['lastnames']
1803 print dto['gender']
1804 print dto['dob']
1805
1806 for key in dto.keys():
1807 print key
1808 #--------------------------------------------------------
1810 # create patient
1811 print '\n\nCreating identity...'
1812 new_identity = create_identity(gender='m', dob='2005-01-01', lastnames='test lastnames', firstnames='test firstnames')
1813 print 'Identity created: %s' % new_identity
1814
1815 print '\nSetting title and gender...'
1816 new_identity['title'] = 'test title';
1817 new_identity['gender'] = 'f';
1818 new_identity.save_payload()
1819 print 'Refetching identity from db: %s' % cIdentity(aPK_obj=new_identity['pk_identity'])
1820
1821 print '\nGetting all names...'
1822 for a_name in new_identity.get_names():
1823 print a_name
1824 print 'Active name: %s' % (new_identity.get_active_name())
1825 print 'Setting nickname...'
1826 new_identity.set_nickname(nickname='test nickname')
1827 print 'Refetching all names...'
1828 for a_name in new_identity.get_names():
1829 print a_name
1830 print 'Active name: %s' % (new_identity.get_active_name())
1831
1832 print '\nIdentity occupations: %s' % new_identity['occupations']
1833 print 'Creating identity occupation...'
1834 new_identity.link_occupation('test occupation')
1835 print 'Identity occupations: %s' % new_identity['occupations']
1836
1837 print '\nIdentity addresses: %s' % new_identity.get_addresses()
1838 print 'Creating identity address...'
1839 # make sure the state exists in the backend
1840 new_identity.link_address (
1841 number = 'test 1234',
1842 street = 'test street',
1843 postcode = 'test postcode',
1844 urb = 'test urb',
1845 state = 'SN',
1846 country = 'DE'
1847 )
1848 print 'Identity addresses: %s' % new_identity.get_addresses()
1849
1850 print '\nIdentity communications: %s' % new_identity.get_comm_channels()
1851 print 'Creating identity communication...'
1852 new_identity.link_comm_channel('homephone', '1234566')
1853 print 'Identity communications: %s' % new_identity.get_comm_channels()
1854 #--------------------------------------------------------
1856 for pk in range(1,16):
1857 name = cPersonName(aPK_obj=pk)
1858 print name.description
1859 print ' ', name
1860 #--------------------------------------------------------
1862 genders, idx = get_gender_list()
1863 print "\n\nRetrieving gender enum (tag, label, weight):"
1864 for gender in genders:
1865 print "%s, %s, %s" % (gender[idx['tag']], gender[idx['l10n_label']], gender[idx['sort_weight']])
1866 #--------------------------------------------------------
1867 #test_dto_person()
1868 #test_identity()
1869 #test_set_active_pat()
1870 #test_search_by_dto()
1871 #test_name()
1872 test_gender_list()
1873
1874 #map_gender2salutation('m')
1875 # module functions
1876
1877 #comms = get_comm_list()
1878 #print "\n\nRetrieving communication media enum (id, description): %s" % comms
1879
1880 #============================================================
1881
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Oct 5 03:56:46 2013 | http://epydoc.sourceforge.net |