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