1
2 """GNUmed patient objects.
3
4 This is a patient object intended to let a useful client-side
5 API crystallize from actual use in true XP fashion.
6 """
7
8 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
9 __license__ = "GPL"
10
11
12 import sys
13 import os.path
14 import time
15 import re as regex
16 import datetime as pyDT
17 import io
18 import threading
19 import logging
20 import io
21 import inspect
22 from xml.etree import ElementTree as etree
23
24
25
26 if __name__ == '__main__':
27 logging.basicConfig(level = logging.DEBUG)
28 sys.path.insert(0, '../../')
29 from Gnumed.pycommon import gmExceptions
30 from Gnumed.pycommon import gmDispatcher
31 from Gnumed.pycommon import gmBorg
32 from Gnumed.pycommon import gmI18N
33 if __name__ == '__main__':
34 gmI18N.activate_locale()
35 gmI18N.install_domain()
36 from Gnumed.pycommon import gmNull
37 from Gnumed.pycommon import gmBusinessDBObject
38 from Gnumed.pycommon import gmTools
39 from Gnumed.pycommon import gmPG2
40 from Gnumed.pycommon import gmDateTime
41 from Gnumed.pycommon import gmMatchProvider
42 from Gnumed.pycommon import gmLog2
43 from Gnumed.pycommon import gmHooks
44
45 from Gnumed.business import gmDemographicRecord
46 from Gnumed.business import gmClinicalRecord
47 from Gnumed.business import gmXdtMappings
48 from Gnumed.business import gmProviderInbox
49 from Gnumed.business import gmExportArea
50 from Gnumed.business import gmBilling
51 from Gnumed.business import gmAutoHints
52 from Gnumed.business.gmDocuments import cDocumentFolder
53
54
55 _log = logging.getLogger('gm.person')
56
57 __gender_list = None
58 __gender_idx = None
59
60 __gender2salutation_map = None
61 __gender2string_map = None
62
63
64 _MERGE_SCRIPT_HEADER = """-- GNUmed patient merge script
65 -- created: %(date)s
66 -- patient to keep : #%(pat2keep)s
67 -- patient to merge: #%(pat2del)s
68 --
69 -- You can EASILY cause mangled data by uncritically applying this script, so ...
70 -- ... BE POSITIVELY SURE YOU UNDERSTAND THE FULL EXTENT OF WHAT IT DOES !
71
72
73 --set default_transaction_read_only to off;
74
75 BEGIN;
76 """
77
78
80 cmd = 'SELECT COUNT(1) FROM dem.lnk_identity2ext_id WHERE fk_origin = %(issuer)s AND external_id = %(val)s'
81 args = {'issuer': pk_issuer, 'val': value}
82 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
83 return rows[0][0]
84
85
86 -def person_exists(lastnames, dob, firstnames=None, active_only=True):
87 args = {
88 'last': lastnames,
89 'dob': dob
90 }
91 where_parts = [
92 "lastnames = %(last)s",
93 "dem.date_trunc_utc('day', dob) = dem.date_trunc_utc('day', %(dob)s)"
94 ]
95 if firstnames is not None:
96 if firstnames.strip() != '':
97
98 where_parts.append("firstnames ~* %(first)s")
99 args['first'] = '\\m' + firstnames
100 if active_only:
101 cmd = """SELECT COUNT(1) FROM dem.v_active_persons WHERE %s""" % ' AND '.join(where_parts)
102 else:
103 cmd = """SELECT COUNT(1) FROM dem.v_all_persons WHERE %s""" % ' AND '.join(where_parts)
104 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
105 return rows[0][0]
106
107
108
110
112 self.identity = None
113 self.external_ids = []
114 self.comm_channels = []
115 self.addresses = []
116
117 self.firstnames = None
118 self.lastnames = None
119 self.title = None
120 self.gender = None
121 self.dob = None
122 self.dob_is_estimated = False
123 self.source = self.__class__.__name__
124
125
126
128 return 'firstnames lastnames dob gender title'.split()
129
132
134 where_snippets = [
135 'firstnames = %(first)s',
136 'lastnames = %(last)s'
137 ]
138 args = {
139 'first': self.firstnames,
140 'last': self.lastnames
141 }
142 if self.dob is not None:
143 where_snippets.append("dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %(dob)s)")
144 args['dob'] = self.dob.replace(hour = 23, minute = 59, second = 59)
145 if self.gender is not None:
146 where_snippets.append('gender = %(sex)s')
147 args['sex'] = self.gender
148 cmd = 'SELECT count(1) FROM dem.v_person_names WHERE %s' % ' AND '.join(where_snippets)
149 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
150
151 return rows[0][0] == 1
152
153 is_unique = property(is_unique, lambda x:x)
154
156 where_snippets = [
157 'firstnames = %(first)s',
158 'lastnames = %(last)s'
159 ]
160 args = {
161 'first': self.firstnames,
162 'last': self.lastnames
163 }
164 if self.dob is not None:
165 where_snippets.append("dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %(dob)s)")
166 args['dob'] = self.dob.replace(hour = 23, minute = 59, second = 59)
167 if self.gender is not None:
168 where_snippets.append('gender = %(sex)s')
169 args['sex'] = self.gender
170 cmd = 'SELECT count(1) FROM dem.v_person_names WHERE %s' % ' AND '.join(where_snippets)
171 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
172
173 return rows[0][0] > 0
174
175 exists = property(exists, lambda x:x)
176
178 """Generate generic queries.
179
180 - not locale dependant
181 - data -> firstnames, lastnames, dob, gender
182
183 shall we mogrify name parts ? probably not as external
184 sources should know what they do
185
186 finds by inactive name, too, but then shows
187 the corresponding active name ;-)
188
189 Returns list of matching identities (may be empty)
190 or None if it was told to create an identity but couldn't.
191 """
192 where_snippets = []
193 args = {}
194
195 where_snippets.append('lower(firstnames) = lower(%(first)s)')
196 args['first'] = self.firstnames
197
198 where_snippets.append('lower(lastnames) = lower(%(last)s)')
199 args['last'] = self.lastnames
200
201 if self.dob is not None:
202 where_snippets.append("dem.date_trunc_utc('day'::text, dob) = dem.date_trunc_utc('day'::text, %(dob)s)")
203 args['dob'] = self.dob.replace(hour = 23, minute = 59, second = 59)
204
205 if self.gender is not None:
206 where_snippets.append('lower(gender) = lower(%(sex)s)')
207 args['sex'] = self.gender
208
209
210 cmd = """
211 SELECT *, '%s' AS match_type
212 FROM dem.v_active_persons
213 WHERE
214 pk_identity IN (
215 SELECT pk_identity FROM dem.v_person_names WHERE %s
216 )
217 ORDER BY lastnames, firstnames, dob""" % (
218 _('external patient source (name, gender, date of birth)'),
219 ' AND '.join(where_snippets)
220 )
221
222 try:
223 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx=True)
224 except:
225 _log.error('cannot get candidate identities for dto "%s"' % self)
226 _log.exception('query %s' % cmd)
227 rows = []
228
229 if len(rows) == 0:
230 _log.debug('no candidate identity matches found')
231 if not can_create:
232 return []
233 ident = self.import_into_database()
234 if ident is None:
235 return None
236 identities = [ident]
237 else:
238 identities = [ cPerson(row = {'pk_field': 'pk_identity', 'data': row, 'idx': idx}) for row in rows ]
239
240 return identities
241
243 """Imports self into the database."""
244
245 self.identity = create_identity (
246 firstnames = self.firstnames,
247 lastnames = self.lastnames,
248 gender = self.gender,
249 dob = self.dob
250 )
251
252 if self.identity is None:
253 return None
254
255 if self.dob_is_estimated:
256 self.identity['dob_is_estimated'] = True
257 if self.title is not None:
258 self.identity['title'] = self.title
259 self.identity.save()
260
261 for ext_id in self.external_ids:
262 try:
263 self.identity.add_external_id (
264 type_name = ext_id['name'],
265 value = ext_id['value'],
266 issuer = ext_id['issuer'],
267 comment = ext_id['comment']
268 )
269 except Exception:
270 _log.exception('cannot import <external ID> from external data source')
271 gmLog2.log_stack_trace()
272
273 for comm in self.comm_channels:
274 try:
275 self.identity.link_comm_channel (
276 comm_medium = comm['channel'],
277 url = comm['url']
278 )
279 except Exception:
280 _log.exception('cannot import <comm channel> from external data source')
281 gmLog2.log_stack_trace()
282
283 for adr in self.addresses:
284 try:
285 self.identity.link_address (
286 adr_type = adr['type'],
287 number = adr['number'],
288 subunit = adr['subunit'],
289 street = adr['street'],
290 postcode = adr['zip'],
291 urb = adr['urb'],
292 region_code = adr['region_code'],
293 country_code = adr['country_code']
294 )
295 except Exception:
296 _log.exception('cannot import <address> from external data source')
297 gmLog2.log_stack_trace()
298
299 return self.identity
300
303
305 value = value.strip()
306 if value == '':
307 return
308 name = name.strip()
309 if name == '':
310 raise ValueError(_('<name> cannot be empty'))
311 issuer = issuer.strip()
312 if issuer == '':
313 raise ValueError(_('<issuer> cannot be empty'))
314 self.external_ids.append({'name': name, 'value': value, 'issuer': issuer, 'comment': comment})
315
317 url = url.strip()
318 if url == '':
319 return
320 channel = channel.strip()
321 if channel == '':
322 raise ValueError(_('<channel> cannot be empty'))
323 self.comm_channels.append({'channel': channel, 'url': url})
324
325 - def remember_address(self, number=None, street=None, urb=None, region_code=None, zip=None, country_code=None, adr_type=None, subunit=None):
326 number = number.strip()
327 if number == '':
328 raise ValueError(_('<number> cannot be empty'))
329 street = street.strip()
330 if street == '':
331 raise ValueError(_('<street> cannot be empty'))
332 urb = urb.strip()
333 if urb == '':
334 raise ValueError(_('<urb> cannot be empty'))
335 zip = zip.strip()
336 if zip == '':
337 raise ValueError(_('<zip> cannot be empty'))
338 country_code = country_code.strip()
339 if country_code == '':
340 raise ValueError(_('<country_code> cannot be empty'))
341 if region_code is not None:
342 region_code = region_code.strip()
343 if region_code in [None, '']:
344 region_code = '??'
345 self.addresses.append ({
346 'type': adr_type,
347 'number': number,
348 'subunit': subunit,
349 'street': street,
350 'zip': zip,
351 'urb': urb,
352 'region_code': region_code,
353 'country_code': country_code
354 })
355
356
357
359 return '<%s (%s) @ %s: %s %s (%s) %s>' % (
360 self.__class__.__name__,
361 self.source,
362 id(self),
363 self.firstnames,
364 self.lastnames,
365 self.gender,
366 self.dob
367 )
368
370 """Do some sanity checks on self.* access."""
371
372 if attr == 'gender':
373 if val is None:
374 object.__setattr__(self, attr, val)
375 return
376 glist, idx = get_gender_list()
377 for gender in glist:
378 if str(val) in [gender[0], gender[1], gender[2], gender[3]]:
379 val = gender[idx['tag']]
380 object.__setattr__(self, attr, val)
381 return
382 raise ValueError('invalid gender: [%s]' % val)
383
384 if attr == 'dob':
385 if val is not None:
386 if not isinstance(val, pyDT.datetime):
387 raise TypeError('invalid type for DOB (must be datetime.datetime): %s [%s]' % (type(val), val))
388 if val.tzinfo is None:
389 raise ValueError('datetime.datetime instance is lacking a time zone: [%s]' % val.isoformat())
390
391 object.__setattr__(self, attr, val)
392 return
393
395 return getattr(self, attr)
396
397
398 -class cPersonName(gmBusinessDBObject.cBusinessDBObject):
399 _cmd_fetch_payload = "SELECT * FROM dem.v_person_names WHERE pk_name = %s"
400 _cmds_store_payload = [
401 """UPDATE dem.names SET
402 active = FALSE
403 WHERE
404 %(active_name)s IS TRUE -- act only when needed and only
405 AND
406 id_identity = %(pk_identity)s -- on names of this identity
407 AND
408 active IS TRUE -- which are active
409 AND
410 id != %(pk_name)s -- but NOT *this* name
411 """,
412 """update dem.names set
413 active = %(active_name)s,
414 preferred = %(preferred)s,
415 comment = %(comment)s
416 where
417 id = %(pk_name)s and
418 id_identity = %(pk_identity)s and -- belt and suspenders
419 xmin = %(xmin_name)s""",
420 """select xmin as xmin_name from dem.names where id = %(pk_name)s"""
421 ]
422 _updatable_fields = ['active_name', 'preferred', 'comment']
423
432
434 return '%(last)s, %(title)s %(first)s%(nick)s' % {
435 'last': self._payload[self._idx['lastnames']],
436 'title': gmTools.coalesce (
437 self._payload[self._idx['title']],
438 map_gender2salutation(self._payload[self._idx['gender']])
439 ),
440 'first': self._payload[self._idx['firstnames']],
441 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], '', " '%s'", '%s')
442 }
443
444 description = property(_get_description, lambda x:x)
445
446
447 _SQL_get_active_person = "SELECT * FROM dem.v_active_persons WHERE pk_identity = %s"
448 _SQL_get_any_person = "SELECT * FROM dem.v_all_persons WHERE pk_identity = %s"
449
450 -class cPerson(gmBusinessDBObject.cBusinessDBObject):
451 _cmd_fetch_payload = _SQL_get_any_person
452 _cmds_store_payload = [
453 """UPDATE dem.identity SET
454 gender = %(gender)s,
455 dob = %(dob)s,
456 dob_is_estimated = %(dob_is_estimated)s,
457 tob = %(tob)s,
458 title = gm.nullify_empty_string(%(title)s),
459 fk_marital_status = %(pk_marital_status)s,
460 deceased = %(deceased)s,
461 emergency_contact = gm.nullify_empty_string(%(emergency_contact)s),
462 fk_emergency_contact = %(pk_emergency_contact)s,
463 fk_primary_provider = %(pk_primary_provider)s,
464 comment = gm.nullify_empty_string(%(comment)s)
465 WHERE
466 pk = %(pk_identity)s and
467 xmin = %(xmin_identity)s
468 RETURNING
469 xmin AS xmin_identity"""
470 ]
471 _updatable_fields = [
472 "title",
473 "dob",
474 "tob",
475 "gender",
476 "pk_marital_status",
477 'deceased',
478 'emergency_contact',
479 'pk_emergency_contact',
480 'pk_primary_provider',
481 'comment',
482 'dob_is_estimated'
483 ]
484
486 return self._payload[self._idx['pk_identity']]
488 raise AttributeError('setting ID of identity is not allowed')
489
490 ID = property(_get_ID, _set_ID)
491
492
494
495 if attribute == 'dob':
496 if value is not None:
497
498 if isinstance(value, pyDT.datetime):
499 if value.tzinfo is None:
500 raise ValueError('datetime.datetime instance is lacking a time zone: [%s]' % dt.isoformat())
501 else:
502 raise TypeError('[%s]: type [%s] (%s) invalid for attribute [dob], must be datetime.datetime or None' % (self.__class__.__name__, type(value), value))
503
504
505 if self._payload[self._idx['dob']] is not None:
506 old_dob = gmDateTime.pydt_strftime (
507 self._payload[self._idx['dob']],
508 format = '%Y %m %d %H %M %S',
509 accuracy = gmDateTime.acc_seconds
510 )
511 new_dob = gmDateTime.pydt_strftime (
512 value,
513 format = '%Y %m %d %H %M %S',
514 accuracy = gmDateTime.acc_seconds
515 )
516 if new_dob == old_dob:
517 return
518
519 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
520
521
524
525
528
533
534 is_patient = property(_get_is_patient, _set_is_patient)
535
536
538 return cPatient(self._payload[self._idx['pk_identity']])
539
540 as_patient = property(_get_as_patient, lambda x:x)
541
542
544 cmd = "SELECT pk FROM dem.staff WHERE fk_identity = %(pk)s"
545 args = {'pk': self._payload[self._idx['pk_identity']]}
546 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
547 if len(rows) == 0:
548 return None
549 return rows[0][0]
550
551 staff_id = property(_get_staff_id, lambda x:x)
552
553
554
555
558
559 gender_symbol = property(_get_gender_symbol, lambda x:x)
560
563
564 gender_string = property(_get_gender_string, lambda x:x)
565
569
570 gender_list = property(_get_gender_list, lambda x:x)
571
573 names = self.get_names(active_only = True)
574 if len(names) == 0:
575 _log.error('cannot retrieve active name for patient [%s]', self._payload[self._idx['pk_identity']])
576 return None
577 return names[0]
578
579 active_name = property(get_active_name, lambda x:x)
580
581 - def get_names(self, active_only=False, exclude_active=False):
582
583 args = {'pk_pat': self._payload[self._idx['pk_identity']]}
584 where_parts = ['pk_identity = %(pk_pat)s']
585 if active_only:
586 where_parts.append('active_name is True')
587 if exclude_active:
588 where_parts.append('active_name is False')
589 cmd = """
590 SELECT *
591 FROM dem.v_person_names
592 WHERE %s
593 ORDER BY active_name DESC, lastnames, firstnames
594 """ % ' AND '.join(where_parts)
595 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
596
597 if len(rows) == 0:
598
599 return []
600
601 names = [ cPersonName(row = {'idx': idx, 'data': r, 'pk_field': 'pk_name'}) for r in rows ]
602 return names
603
605 if with_nickname:
606 template = _('%(last)s,%(title)s %(first)s%(nick)s (%(sex)s)')
607 else:
608 template = _('%(last)s,%(title)s %(first)s (%(sex)s)')
609 return template % {
610 'last': self._payload[self._idx['lastnames']],
611 'title': gmTools.coalesce(self._payload[self._idx['title']], '', ' %s'),
612 'first': self._payload[self._idx['firstnames']],
613 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], '', " '%s'"),
614 'sex': self.gender_symbol
615 }
616
617
619 if with_nickname:
620 template = _('%(last)s,%(title)s %(first)s%(nick)s')
621 else:
622 template = _('%(last)s,%(title)s %(first)s')
623 return template % {
624 'last': self._payload[self._idx['lastnames']],
625 'title': gmTools.coalesce(self._payload[self._idx['title']], '', ' %s'),
626 'first': self._payload[self._idx['firstnames']],
627 'nick': gmTools.coalesce(self._payload[self._idx['preferred']], '', " '%s'")
628 }
629
630
631 - def add_name(self, firstnames, lastnames, active=True):
632 """Add a name.
633
634 @param firstnames The first names.
635 @param lastnames The last names.
636 @param active When True, the new name will become the active one (hence setting other names to inactive)
637 @type active A bool instance
638 """
639 name = create_name(self.ID, firstnames, lastnames, active)
640 if active:
641 self.refetch_payload()
642 return name
643
644
646 cmd = "delete from dem.names where id = %(name)s and id_identity = %(pat)s"
647 args = {'name': name['pk_name'], 'pat': self.ID}
648 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
649
650
651
652
653
655 """
656 Set the nickname. Setting the nickname only makes sense for the currently
657 active name.
658 @param nickname The preferred/nick/warrior name to set.
659 """
660 if self._payload[self._idx['preferred']] == nickname:
661 return True
662 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': "SELECT dem.set_nickname(%s, %s)", 'args': [self.ID, nickname]}])
663
664
665
666
667 self._payload[self._idx['preferred']] = nickname
668
669 return True
670
671
682
683 tags = property(get_tags, lambda x:x)
684
685
687 args = {
688 'tag': tag,
689 'identity': self.ID
690 }
691
692
693 cmd = "SELECT pk FROM dem.identity_tag WHERE fk_tag = %(tag)s AND fk_identity = %(identity)s"
694 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
695 if len(rows) > 0:
696 return gmDemographicRecord.cPersonTag(aPK_obj = rows[0]['pk'])
697
698
699 cmd = """
700 INSERT INTO dem.identity_tag (
701 fk_tag,
702 fk_identity
703 ) VALUES (
704 %(tag)s,
705 %(identity)s
706 )
707 RETURNING pk
708 """
709 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
710 return gmDemographicRecord.cPersonTag(aPK_obj = rows[0]['pk'])
711
712
714 cmd = "DELETE FROM dem.identity_tag WHERE pk = %(pk)s"
715 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': tag}}])
716
717
718
719
720
721
722
723
724
725
726
727 - def add_external_id(self, type_name=None, value=None, issuer=None, comment=None, pk_type=None):
728 """Adds an external ID to the patient.
729
730 creates ID type if necessary
731 """
732
733 if pk_type is not None:
734 cmd = """
735 select * from dem.v_external_ids4identity where
736 pk_identity = %(pat)s and
737 pk_type = %(pk_type)s and
738 value = %(val)s"""
739 else:
740
741 if issuer is None:
742 cmd = """
743 select * from dem.v_external_ids4identity where
744 pk_identity = %(pat)s and
745 name = %(name)s and
746 value = %(val)s"""
747 else:
748 cmd = """
749 select * from dem.v_external_ids4identity where
750 pk_identity = %(pat)s and
751 name = %(name)s and
752 value = %(val)s and
753 issuer = %(issuer)s"""
754 args = {
755 'pat': self.ID,
756 'name': type_name,
757 'val': value,
758 'issuer': issuer,
759 'pk_type': pk_type
760 }
761 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
762
763
764 if len(rows) == 0:
765
766 args = {
767 'pat': self.ID,
768 'val': value,
769 'type_name': type_name,
770 'pk_type': pk_type,
771 'issuer': issuer,
772 'comment': comment
773 }
774
775 if pk_type is None:
776 cmd = """insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values (
777 %(val)s,
778 (select dem.add_external_id_type(%(type_name)s, %(issuer)s)),
779 %(comment)s,
780 %(pat)s
781 )"""
782 else:
783 cmd = """insert into dem.lnk_identity2ext_id (external_id, fk_origin, comment, id_identity) values (
784 %(val)s,
785 %(pk_type)s,
786 %(comment)s,
787 %(pat)s
788 )"""
789
790 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
791
792
793 else:
794 row = rows[0]
795 if comment is not None:
796
797 if gmTools.coalesce(row['comment'], '').find(comment.strip()) == -1:
798 comment = '%s%s' % (gmTools.coalesce(row['comment'], '', '%s // '), comment.strip)
799 cmd = "update dem.lnk_identity2ext_id set comment = %(comment)s where id=%(pk)s"
800 args = {'comment': comment, 'pk': row['pk_id']}
801 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
802
803
804 - def update_external_id(self, pk_id=None, type=None, value=None, issuer=None, comment=None):
805 """Edits an existing external ID.
806
807 Creates ID type if necessary.
808 """
809 cmd = """
810 UPDATE dem.lnk_identity2ext_id SET
811 fk_origin = (SELECT dem.add_external_id_type(%(type)s, %(issuer)s)),
812 external_id = %(value)s,
813 comment = gm.nullify_empty_string(%(comment)s)
814 WHERE
815 id = %(pk)s
816 """
817 args = {'pk': pk_id, 'value': value, 'type': type, 'issuer': issuer, 'comment': comment}
818 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
819
820
822 where_parts = ['pk_identity = %(pat)s']
823 args = {'pat': self.ID}
824
825 if id_type is not None:
826 where_parts.append('name = %(name)s')
827 args['name'] = id_type.strip()
828
829 if issuer is not None:
830 where_parts.append('issuer = %(issuer)s')
831 args['issuer'] = issuer.strip()
832
833 cmd = "SELECT * FROM dem.v_external_ids4identity WHERE %s" % ' AND '.join(where_parts)
834 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
835
836 return rows
837
838 external_ids = property(get_external_ids, lambda x:x)
839
840
842 cmd = """
843 DELETE FROM dem.lnk_identity2ext_id
844 WHERE id_identity = %(pat)s AND id = %(pk)s"""
845 args = {'pat': self.ID, 'pk': pk_ext_id}
846 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
847
848
850 name = self.active_name
851 last = ' '.join(p for p in name['lastnames'].split("-"))
852 last = ' '.join(p for p in last.split("."))
853 last = ' '.join(p for p in last.split("'"))
854 last = ''.join(gmTools.capitalize(text = p, mode = gmTools.CAPS_FIRST_ONLY) for p in last.split(' '))
855 first = ' '.join(p for p in name['firstnames'].split("-"))
856 first = ' '.join(p for p in first.split("."))
857 first = ' '.join(p for p in first.split("'"))
858 first = ''.join(gmTools.capitalize(text = p, mode = gmTools.CAPS_FIRST_ONLY) for p in first.split(' '))
859 suggestion = 'GMd-%s%s%s%s%s' % (
860 gmTools.coalesce(target, '', '%s-'),
861 last,
862 first,
863 self.get_formatted_dob(format = '-%Y%m%d', none_string = ''),
864 gmTools.coalesce(self['gender'], '', '-%s')
865 )
866 try:
867 import unidecode
868 return unidecode.unidecode(suggestion)
869 except ImportError:
870 _log.debug('cannot transliterate external ID suggestion, <unidecode> module not installed')
871 if encoding is None:
872 return suggestion
873 return suggestion.encode(encoding)
874
875 external_id_suggestion = property(suggest_external_id, lambda x:x)
876
877
879 names2use = [self.active_name]
880 names2use.extend(self.get_names(active_only = False, exclude_active = True))
881 target = gmTools.coalesce(target, '', '%s-')
882 dob = self.get_formatted_dob(format = '-%Y%m%d', none_string = '')
883 gender = gmTools.coalesce(self['gender'], '', '-%s')
884 suggestions = []
885 for name in names2use:
886 last = ' '.join(p for p in name['lastnames'].split("-"))
887 last = ' '.join(p for p in last.split("."))
888 last = ' '.join(p for p in last.split("'"))
889 last = ''.join(gmTools.capitalize(text = p, mode = gmTools.CAPS_FIRST_ONLY) for p in last.split(' '))
890 first = ' '.join(p for p in name['firstnames'].split("-"))
891 first = ' '.join(p for p in first.split("."))
892 first = ' '.join(p for p in first.split("'"))
893 first = ''.join(gmTools.capitalize(text = p, mode = gmTools.CAPS_FIRST_ONLY) for p in first.split(' '))
894 suggestion = 'GMd-%s%s%s%s%s' % (target, last, first, dob, gender)
895 try:
896 import unidecode
897 suggestions.append(unidecode.unidecode(suggestion))
898 continue
899 except ImportError:
900 _log.debug('cannot transliterate external ID suggestion, <unidecode> module not installed')
901 if encoding is None:
902 suggestions.append(suggestion)
903 else:
904 suggestions.append(suggestion.encode(encoding))
905 return suggestions
906
907
908
910 """Merge another identity into this one.
911
912 Keep this one. Delete other one."""
913
914 if other_identity.ID == self.ID:
915 return True, None
916
917 curr_pat = gmCurrentPatient()
918 if curr_pat.connected:
919 if other_identity.ID == curr_pat.ID:
920 return False, _('Cannot merge active patient into another patient.')
921
922 now_here = gmDateTime.pydt_strftime(gmDateTime.pydt_now_here())
923 distinguisher = _('merge of #%s into #%s @ %s') % (other_identity.ID, self.ID, now_here)
924
925 queries = []
926 args = {'pat2del': other_identity.ID, 'pat2keep': self.ID}
927
928
929 queries.append ({
930 'cmd': """
931 UPDATE clin.allergy_state SET
932 has_allergy = greatest (
933 (SELECT has_allergy FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2del)s),
934 (SELECT has_allergy FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2keep)s)
935 ),
936 -- perhaps use least() to play it safe and make it appear longer ago than it might have been, actually ?
937 last_confirmed = greatest (
938 (SELECT last_confirmed FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2del)s),
939 (SELECT last_confirmed FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2keep)s)
940 )
941 WHERE
942 pk = (SELECT pk_allergy_state FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2keep)s)
943 """,
944 'args': args
945 })
946
947 queries.append ({
948 'cmd': 'DELETE FROM clin.allergy_state WHERE pk = (SELECT pk_allergy_state FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat2del)s)',
949 'args': args
950 })
951
952
953 queries.append ({
954 'cmd': """
955 UPDATE clin.patient SET
956 edc = coalesce (
957 edc,
958 (SELECT edc FROM clin.patient WHERE fk_identity = %(pat2del)s)
959 )
960 WHERE
961 fk_identity = %(pat2keep)s
962 """,
963 'args': args
964 })
965
966
967
968 queries.append ({
969 'cmd': """
970 UPDATE dem.names d_n1 SET
971 lastnames = lastnames || coalesce (
972 ' (from "' || (SELECT comment FROM dem.identity WHERE pk = %%(pat2del)s) || '")',
973 ' (%s)'
974 )
975 WHERE
976 d_n1.id_identity = %%(pat2del)s
977 AND
978 EXISTS (
979 SELECT 1 FROM dem.names d_n2
980 WHERE
981 d_n2.id_identity = %%(pat2keep)s
982 AND
983 d_n2.lastnames = d_n1.lastnames
984 AND
985 d_n2.firstnames = d_n1.firstnames
986 )""" % distinguisher,
987 'args': args
988 })
989
990 queries.append ({
991 'cmd': """
992 UPDATE dem.names SET
993 id_identity = %%(pat2keep)s,
994 comment = coalesce(comment || ' (%s)', '%s')
995 WHERE
996 id_identity = %%(pat2del)s
997 AND
998 active IS false
999 """ % (distinguisher, distinguisher),
1000 'args': args
1001 })
1002
1003
1004
1005
1006
1007
1008 queries.append ({
1009 'cmd': """
1010 INSERT INTO dem.names (
1011 id_identity, active, lastnames, firstnames, preferred, comment
1012 )
1013 SELECT
1014 %%(pat2keep)s, false, lastnames, firstnames, preferred, coalesce(comment || ' (%s)', '%s')
1015 FROM dem.names d_n
1016 WHERE
1017 d_n.id_identity = %%(pat2del)s
1018 AND
1019 d_n.active IS true
1020 """ % (distinguisher, distinguisher),
1021 'args': args
1022 })
1023
1024
1025
1026 queries.append ({
1027 'cmd': """
1028 UPDATE dem.lnk_identity2comm
1029 SET url = url || ' (%s)'
1030 WHERE
1031 fk_identity = %%(pat2del)s
1032 AND
1033 EXISTS (
1034 SELECT 1 FROM dem.lnk_identity2comm d_li2c
1035 WHERE d_li2c.fk_identity = %%(pat2keep)s AND d_li2c.url = url
1036 )
1037 """ % distinguisher,
1038 'args': args
1039 })
1040
1041 queries.append ({
1042 'cmd': """
1043 UPDATE dem.lnk_identity2ext_id
1044 SET external_id = external_id || ' (%s)'
1045 WHERE
1046 id_identity = %%(pat2del)s
1047 AND
1048 EXISTS (
1049 SELECT 1 FROM dem.lnk_identity2ext_id d_li2e
1050 WHERE
1051 d_li2e.id_identity = %%(pat2keep)s
1052 AND
1053 d_li2e.external_id = external_id
1054 AND
1055 d_li2e.fk_origin = fk_origin
1056 )
1057 """ % distinguisher,
1058 'args': args
1059 })
1060
1061 queries.append ({
1062 'cmd': """
1063 DELETE FROM dem.lnk_person_org_address
1064 WHERE
1065 id_identity = %(pat2del)s
1066 AND
1067 id_address IN (
1068 SELECT id_address FROM dem.lnk_person_org_address d_lpoa
1069 WHERE d_lpoa.id_identity = %(pat2keep)s
1070 )
1071 """,
1072 'args': args
1073 })
1074
1075
1076 FKs = gmPG2.get_foreign_keys2column (
1077 schema = 'dem',
1078 table = 'identity',
1079 column = 'pk'
1080 )
1081
1082 FKs.extend (gmPG2.get_foreign_keys2column (
1083 schema = 'clin',
1084 table = 'patient',
1085 column = 'fk_identity'
1086 ))
1087
1088
1089 cmd_template = 'UPDATE %s SET %s = %%(pat2keep)s WHERE %s = %%(pat2del)s'
1090 for FK in FKs:
1091 if FK['referencing_table'] in ['dem.names', 'clin.patient']:
1092 continue
1093 queries.append ({
1094 'cmd': cmd_template % (FK['referencing_table'], FK['referencing_column'], FK['referencing_column']),
1095 'args': args
1096 })
1097
1098
1099 queries.append ({
1100 'cmd': 'DELETE FROM clin.patient WHERE fk_identity = %(pat2del)s',
1101 'args': args
1102 })
1103
1104
1105 queries.append ({
1106 'cmd': 'delete from dem.identity where pk = %(pat2del)s',
1107 'args': args
1108 })
1109
1110 script_name = gmTools.get_unique_filename(prefix = 'gm-assimilate-%(pat2del)s-into-%(pat2keep)s-' % args, suffix = '.sql')
1111 _log.warning('identity [%s] is about to assimilate identity [%s], SQL script [%s]', self.ID, other_identity.ID, script_name)
1112
1113 script = io.open(script_name, 'wt')
1114 args['date'] = gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), '%Y %B %d %H:%M')
1115 script.write(_MERGE_SCRIPT_HEADER % args)
1116 for query in queries:
1117 script.write(query['cmd'] % args)
1118 script.write(';\n')
1119 script.write('\nROLLBACK;\n')
1120 script.write('--COMMIT;\n')
1121 script.close()
1122
1123 gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, end_tx = True)
1124
1125 self.add_external_id (
1126 type_name = 'merged GNUmed identity primary key',
1127 value = 'GNUmed::pk::%s' % other_identity.ID,
1128 issuer = 'GNUmed'
1129 )
1130
1131 return True, None
1132
1133
1134
1136 cmd = """
1137 insert into clin.waiting_list (fk_patient, urgency, comment, area, list_position)
1138 values (
1139 %(pat)s,
1140 %(urg)s,
1141 %(cmt)s,
1142 %(area)s,
1143 (select coalesce((max(list_position) + 1), 1) from clin.waiting_list)
1144 )"""
1145 args = {'pat': self.ID, 'urg': urgency, 'cmt': comment, 'area': zone}
1146 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], verbose = True)
1147
1149 cmd = """SELECT * FROM clin.v_waiting_list WHERE pk_identity = %(pat)s"""
1150 args = {'pat': self.ID}
1151 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1152 return rows
1153
1154 waiting_list_entries = property(get_waiting_list_entry, lambda x:x)
1155
1158
1159 export_area = property(_get_export_area, lambda x:x)
1160
1161 - def export_as_gdt(self, filename=None, encoding='iso-8859-15', external_id_type=None):
1162
1163 template = '%s%s%s\r\n'
1164
1165 if filename is None:
1166 filename = gmTools.get_unique_filename (
1167 prefix = 'gm-patient-',
1168 suffix = '.gdt'
1169 )
1170
1171 gdt_file = io.open(filename, mode = 'wt', encoding = encoding, errors = 'strict')
1172
1173 gdt_file.write(template % ('013', '8000', '6301'))
1174 gdt_file.write(template % ('013', '9218', '2.10'))
1175 if external_id_type is None:
1176 gdt_file.write(template % ('%03d' % (9 + len(str(self.ID))), '3000', self.ID))
1177 else:
1178 ext_ids = self.get_external_ids(id_type = external_id_type)
1179 if len(ext_ids) > 0:
1180 gdt_file.write(template % ('%03d' % (9 + len(ext_ids[0]['value'])), '3000', ext_ids[0]['value']))
1181 gdt_file.write(template % ('%03d' % (9 + len(self._payload[self._idx['lastnames']])), '3101', self._payload[self._idx['lastnames']]))
1182 gdt_file.write(template % ('%03d' % (9 + len(self._payload[self._idx['firstnames']])), '3102', self._payload[self._idx['firstnames']]))
1183 gdt_file.write(template % ('%03d' % (9 + len(self._payload[self._idx['dob']].strftime('%d%m%Y'))), '3103', self._payload[self._idx['dob']].strftime('%d%m%Y')))
1184 gdt_file.write(template % ('010', '3110', gmXdtMappings.map_gender_gm2xdt[self._payload[self._idx['gender']]]))
1185 gdt_file.write(template % ('025', '6330', 'GNUmed::9206::encoding'))
1186 gdt_file.write(template % ('%03d' % (9 + len(encoding)), '6331', encoding))
1187 if external_id_type is None:
1188 gdt_file.write(template % ('029', '6332', 'GNUmed::3000::source'))
1189 gdt_file.write(template % ('017', '6333', 'internal'))
1190 else:
1191 if len(ext_ids) > 0:
1192 gdt_file.write(template % ('029', '6332', 'GNUmed::3000::source'))
1193 gdt_file.write(template % ('%03d' % (9 + len(external_id_type)), '6333', external_id_type))
1194
1195 gdt_file.close()
1196
1197 return filename
1198
1200
1201 if filename is None:
1202 filename = gmTools.get_unique_filename (
1203 prefix = 'gm-LinuxMedNews_demographics-',
1204 suffix = '.xml'
1205 )
1206
1207 dob_format = '%Y-%m-%d'
1208 pat = etree.Element('patient')
1209
1210 first = etree.SubElement(pat, 'firstname')
1211 first.text = gmTools.coalesce(self._payload[self._idx['firstnames']], '')
1212
1213 last = etree.SubElement(pat, 'lastname')
1214 last.text = gmTools.coalesce(self._payload[self._idx['lastnames']], '')
1215
1216
1217
1218
1219
1220
1221 pref = etree.SubElement(pat, 'name_prefix')
1222 pref.text = gmTools.coalesce(self._payload[self._idx['title']], '')
1223
1224 suff = etree.SubElement(pat, 'name_suffix')
1225 suff.text = ''
1226
1227 dob = etree.SubElement(pat, 'DOB')
1228 dob.set('format', dob_format)
1229 dob.text = gmDateTime.pydt_strftime(self._payload[self._idx['dob']], dob_format, accuracy = gmDateTime.acc_days, none_str = '')
1230
1231 gender = etree.SubElement(pat, 'gender')
1232 gender.set('comment', self.gender_string)
1233 if self._payload[self._idx['gender']] is None:
1234 gender.text = ''
1235 else:
1236 gender.text = map_gender2mf[self._payload[self._idx['gender']]]
1237
1238 home = etree.SubElement(pat, 'home_address')
1239 adrs = self.get_addresses(address_type = 'home')
1240 if len(adrs) > 0:
1241 adr = adrs[0]
1242 city = etree.SubElement(home, 'city')
1243 city.set('comment', gmTools.coalesce(adr['suburb'], ''))
1244 city.text = gmTools.coalesce(adr['urb'], '')
1245
1246 region = etree.SubElement(home, 'region')
1247 region.set('comment', gmTools.coalesce(adr['l10n_region'], ''))
1248 region.text = gmTools.coalesce(adr['code_region'], '')
1249
1250 zipcode = etree.SubElement(home, 'postal_code')
1251 zipcode.text = gmTools.coalesce(adr['postcode'], '')
1252
1253 street = etree.SubElement(home, 'street')
1254 street.set('comment', gmTools.coalesce(adr['notes_street'], ''))
1255 street.text = gmTools.coalesce(adr['street'], '')
1256
1257 no = etree.SubElement(home, 'number')
1258 no.set('subunit', gmTools.coalesce(adr['subunit'], ''))
1259 no.set('comment', gmTools.coalesce(adr['notes_subunit'], ''))
1260 no.text = gmTools.coalesce(adr['number'], '')
1261
1262 country = etree.SubElement(home, 'country')
1263 country.set('comment', adr['l10n_country'])
1264 country.text = gmTools.coalesce(adr['code_country'], '')
1265
1266 phone = etree.SubElement(pat, 'home_phone')
1267 rec = self.get_comm_channels(comm_medium = 'homephone')
1268 if len(rec) > 0:
1269 if not rec[0]['is_confidential']:
1270 phone.set('comment', gmTools.coalesce(rec[0]['comment'], ''))
1271 phone.text = rec[0]['url']
1272
1273 phone = etree.SubElement(pat, 'work_phone')
1274 rec = self.get_comm_channels(comm_medium = 'workphone')
1275 if len(rec) > 0:
1276 if not rec[0]['is_confidential']:
1277 phone.set('comment', gmTools.coalesce(rec[0]['comment'], ''))
1278 phone.text = rec[0]['url']
1279
1280 phone = etree.SubElement(pat, 'cell_phone')
1281 rec = self.get_comm_channels(comm_medium = 'mobile')
1282 if len(rec) > 0:
1283 if not rec[0]['is_confidential']:
1284 phone.set('comment', gmTools.coalesce(rec[0]['comment'], ''))
1285 phone.text = rec[0]['url']
1286
1287 tree = etree.ElementTree(pat)
1288 tree.write(filename, encoding = 'UTF-8')
1289
1290 return filename
1291
1292
1294
1295
1296
1297
1298
1299 dob_format = '%Y%m%d'
1300
1301 import vobject
1302
1303 vc = vobject.vCard()
1304 vc.add('kind')
1305 vc.kind.value = 'individual'
1306
1307 vc.add('fn')
1308 vc.fn.value = self.get_description()
1309 vc.add('n')
1310 vc.n.value = vobject.vcard.Name(family = self._payload[self._idx['lastnames']], given = self._payload[self._idx['firstnames']])
1311
1312
1313
1314 vc.add('title')
1315 vc.title.value = gmTools.coalesce(self._payload[self._idx['title']], '')
1316 vc.add('gender')
1317
1318 vc.gender.value = map_gender2vcard[self._payload[self._idx['gender']]]
1319 vc.add('bday')
1320 vc.bday.value = gmDateTime.pydt_strftime(self._payload[self._idx['dob']], dob_format, accuracy = gmDateTime.acc_days, none_str = '')
1321
1322 channels = self.get_comm_channels(comm_medium = 'homephone')
1323 if len(channels) > 0:
1324 if not channels[0]['is_confidential']:
1325 vc.add('tel')
1326 vc.tel.value = channels[0]['url']
1327 vc.tel.type_param = 'HOME'
1328 channels = self.get_comm_channels(comm_medium = 'workphone')
1329 if len(channels) > 0:
1330 if not channels[0]['is_confidential']:
1331 vc.add('tel')
1332 vc.tel.value = channels[0]['url']
1333 vc.tel.type_param = 'WORK'
1334 channels = self.get_comm_channels(comm_medium = 'mobile')
1335 if len(channels) > 0:
1336 if not channels[0]['is_confidential']:
1337 vc.add('tel')
1338 vc.tel.value = channels[0]['url']
1339 vc.tel.type_param = 'CELL'
1340 channels = self.get_comm_channels(comm_medium = 'fax')
1341 if len(channels) > 0:
1342 if not channels[0]['is_confidential']:
1343 vc.add('tel')
1344 vc.tel.value = channels[0]['url']
1345 vc.tel.type_param = 'FAX'
1346 channels = self.get_comm_channels(comm_medium = 'email')
1347 if len(channels) > 0:
1348 if not channels[0]['is_confidential']:
1349 vc.add('email')
1350 vc.tel.value = channels[0]['url']
1351 vc.tel.type_param = 'INTERNET'
1352 channels = self.get_comm_channels(comm_medium = 'web')
1353 if len(channels) > 0:
1354 if not channels[0]['is_confidential']:
1355 vc.add('url')
1356 vc.tel.value = channels[0]['url']
1357 vc.tel.type_param = 'INTERNET'
1358
1359 adrs = self.get_addresses(address_type = 'home')
1360 if len(adrs) > 0:
1361 home_adr = adrs[0]
1362 vc.add('adr')
1363 vc.adr.type_param = 'HOME'
1364 vc.adr.value = vobject.vcard.Address()
1365 vc_adr = vc.adr.value
1366 vc_adr.extended = gmTools.coalesce(home_adr['subunit'], '')
1367 vc_adr.street = gmTools.coalesce(home_adr['street'], '', '%s ') + gmTools.coalesce(home_adr['number'], '')
1368 vc_adr.region = gmTools.coalesce(home_adr['l10n_region'], '')
1369 vc_adr.code = gmTools.coalesce(home_adr['postcode'], '')
1370 vc_adr.city = gmTools.coalesce(home_adr['urb'], '')
1371 vc_adr.country = gmTools.coalesce(home_adr['l10n_country'], '')
1372
1373
1374
1375 if filename is None:
1376 filename = gmTools.get_unique_filename (
1377 prefix = 'gm-patient-',
1378 suffix = '.vcf'
1379 )
1380 vcf = io.open(filename, mode = 'wt', encoding = 'utf8')
1381 try:
1382 vcf.write(vc.serialize().decode('utf-8'))
1383 except UnicodeDecodeError:
1384 _log.exception('failed to serialize VCF data')
1385 vcf.close()
1386 return 'cannot-serialize.vcf'
1387 vcf.close()
1388
1389 return filename
1390
1391
1392
1395
1396
1398 """Link an occupation with a patient, creating the occupation if it does not exists.
1399
1400 @param occupation The name of the occupation to link the patient to.
1401 """
1402 if (activities is None) and (occupation is None):
1403 return True
1404
1405 occupation = occupation.strip()
1406 if len(occupation) == 0:
1407 return True
1408
1409 if activities is not None:
1410 activities = activities.strip()
1411
1412 args = {'act': activities, 'pat_id': self.pk_obj, 'job': occupation}
1413
1414 cmd = "select activities from dem.v_person_jobs where pk_identity = %(pat_id)s and l10n_occupation = _(%(job)s)"
1415 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1416
1417 queries = []
1418 if len(rows) == 0:
1419 queries.append ({
1420 'cmd': "INSERT INTO dem.lnk_job2person (fk_identity, fk_occupation, activities) VALUES (%(pat_id)s, dem.create_occupation(%(job)s), %(act)s)",
1421 'args': args
1422 })
1423 else:
1424 if rows[0]['activities'] != activities:
1425 queries.append ({
1426 'cmd': "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))",
1427 'args': args
1428 })
1429
1430 rows, idx = gmPG2.run_rw_queries(queries = queries)
1431
1432 return True
1433
1435 if occupation is None:
1436 return True
1437 occupation = occupation.strip()
1438 cmd = "delete from dem.lnk_job2person where fk_identity=%(pk)s and fk_occupation in (select id from dem.occupation where _(name) = _(%(job)s))"
1439 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj, 'job': occupation}}])
1440 return True
1441
1442
1443
1445 cmd = "select * from dem.v_person_comms where pk_identity = %s"
1446 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}], get_col_idx = True)
1447
1448 filtered = rows
1449
1450 if comm_medium is not None:
1451 filtered = []
1452 for row in rows:
1453 if row['comm_type'] == comm_medium:
1454 filtered.append(row)
1455
1456 return [ gmDemographicRecord.cCommChannel(row = {
1457 'pk_field': 'pk_lnk_identity2comm',
1458 'data': r,
1459 'idx': idx
1460 }) for r in filtered
1461 ]
1462
1463 comm_channels = property(get_comm_channels, lambda x:x)
1464
1465 - def link_comm_channel(self, comm_medium=None, url=None, is_confidential=False, pk_channel_type=None):
1466 """Link a communication medium with a patient.
1467
1468 @param comm_medium The name of the communication medium.
1469 @param url The communication resource locator.
1470 @type url A str instance.
1471 @param is_confidential Wether the data must be treated as confidential.
1472 @type is_confidential A bool instance.
1473 """
1474 comm_channel = gmDemographicRecord.create_comm_channel (
1475 comm_medium = comm_medium,
1476 url = url,
1477 is_confidential = is_confidential,
1478 pk_channel_type = pk_channel_type,
1479 pk_identity = self.pk_obj
1480 )
1481 return comm_channel
1482
1488
1489
1490
1492
1493 cmd = "SELECT * FROM dem.v_pat_addresses WHERE pk_identity = %(pat)s"
1494 args = {'pat': self.pk_obj}
1495 if address_type is not None:
1496 cmd = cmd + " AND address_type = %(typ)s"
1497 args['typ'] = address_type
1498
1499 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1500
1501 return [
1502 gmDemographicRecord.cPatientAddress(row = {'idx': idx, 'data': r, 'pk_field': 'pk_address'})
1503 for r in rows
1504 ]
1505
1506 - def link_address(self, number=None, street=None, postcode=None, urb=None, region_code=None, country_code=None, subunit=None, suburb=None, id_type=None, address=None, adr_type=None):
1507 """Link an address with a patient, creating the address if it does not exists.
1508
1509 @param id_type The primary key of the address type.
1510 """
1511 if address is None:
1512 address = gmDemographicRecord.create_address (
1513 country_code = country_code,
1514 region_code = region_code,
1515 urb = urb,
1516 suburb = suburb,
1517 postcode = postcode,
1518 street = street,
1519 number = number,
1520 subunit = subunit
1521 )
1522
1523 if address is None:
1524 return None
1525
1526
1527 cmd = "SELECT id_address FROM dem.lnk_person_org_address WHERE id_identity = %(pat)s AND id_address = %(adr)s"
1528 args = {'pat': self.pk_obj, 'adr': address['pk_address']}
1529 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1530
1531
1532 if len(rows) == 0:
1533 args = {'id': self.pk_obj, 'adr': address['pk_address'], 'type': id_type}
1534 cmd = """
1535 INSERT INTO dem.lnk_person_org_address(id_identity, id_address)
1536 VALUES (%(id)s, %(adr)s)
1537 RETURNING id_address"""
1538 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
1539
1540 linked_adr = gmDemographicRecord.cPatientAddress(aPK_obj = rows[0]['id_address'])
1541
1542
1543 if id_type is None:
1544 if adr_type is not None:
1545 id_type = gmDemographicRecord.create_address_type(address_type = adr_type)
1546 if id_type is not None:
1547 linked_adr['pk_address_type'] = id_type
1548 linked_adr.save()
1549
1550 return linked_adr
1551
1553 """Remove an address from the patient.
1554
1555 The address itself stays in the database.
1556 The address can be either cAdress or cPatientAdress.
1557 """
1558 if pk_address is None:
1559 args = {'person': self.pk_obj, 'adr': address['pk_address']}
1560 else:
1561 args = {'person': self.pk_obj, 'adr': pk_address}
1562 cmd = """
1563 DELETE FROM dem.lnk_person_org_address
1564 WHERE
1565 dem.lnk_person_org_address.id_identity = %(person)s
1566 AND
1567 dem.lnk_person_org_address.id_address = %(adr)s
1568 AND
1569 NOT EXISTS(SELECT 1 FROM bill.bill WHERE fk_receiver_address = dem.lnk_person_org_address.id)
1570 """
1571 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1572
1573
1574
1575 - def get_bills(self, order_by=None, pk_patient=None):
1576 return gmBilling.get_bills (
1577 order_by = order_by,
1578 pk_patient = self.pk_obj
1579 )
1580
1581 bills = property(get_bills, lambda x:x)
1582
1583
1584
1586 cmd = """
1587 SELECT
1588 d_rt.description,
1589 d_vap.*
1590 FROM
1591 dem.v_all_persons d_vap,
1592 dem.relation_types d_rt,
1593 dem.lnk_person2relative d_lp2r
1594 WHERE
1595 ( d_lp2r.id_identity = %(pk)s
1596 AND
1597 d_vap.pk_identity = d_lp2r.id_relative
1598 AND
1599 d_rt.id = d_lp2r.id_relation_type
1600 ) or (
1601 d_lp2r.id_relative = %(pk)s
1602 AND
1603 d_vap.pk_identity = d_lp2r.id_identity
1604 AND
1605 d_rt.inverse = d_lp2r.id_relation_type
1606 )"""
1607 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
1608 if len(rows) == 0:
1609 return []
1610 return [(row[0], cPerson(row = {'data': row[1:], 'idx':idx, 'pk_field': 'pk_identity'})) for row in rows]
1611
1613
1614 id_new_relative = create_dummy_identity()
1615
1616 relative = cPerson(aPK_obj=id_new_relative)
1617
1618
1619 relative.add_name( '**?**', self.get_names()['lastnames'])
1620
1621 if 'relatives' in self._ext_cache:
1622 del self._ext_cache['relatives']
1623 cmd = """
1624 insert into dem.lnk_person2relative (
1625 id_identity, id_relative, id_relation_type
1626 ) values (
1627 %s, %s, (select id from dem.relation_types where description = %s)
1628 )"""
1629 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [self.ID, id_new_relative, rel_type ]}])
1630 return True
1631
1633
1634 self.set_relative(None, relation)
1635
1640
1641 emergency_contact_in_database = property(_get_emergency_contact_from_database, lambda x:x)
1642
1643
1644
1645
1653
1654
1695
1696
1697 - def dob_in_range(self, min_distance='1 week', max_distance='1 week'):
1698 if self['dob'] is None:
1699 return False
1700 cmd = 'select dem.dob_is_in_range(%(dob)s, %(min)s, %(max)s)'
1701 rows, idx = gmPG2.run_ro_queries (
1702 queries = [{
1703 'cmd': cmd,
1704 'args': {'dob': self['dob'], 'min': min_distance, 'max': max_distance}
1705 }]
1706 )
1707 return rows[0][0]
1708
1709
1711 if self['dob'] is None:
1712 return None
1713 now = gmDateTime.pydt_now_here()
1714 if now.month < self['dob'].month:
1715 return False
1716 if now.month > self['dob'].month:
1717 return True
1718
1719 if now.day < self['dob'].day:
1720 return False
1721 if now.day > self['dob'].day:
1722 return True
1723
1724 return False
1725
1726 current_birthday_passed = property(_get_current_birthday_passed)
1727
1728
1738
1739 birthday_this_year = property(_get_birthday_this_year)
1740
1741
1751
1752 birthday_next_year = property(_get_birthday_next_year)
1753
1754
1764
1765 birthday_last_year = property(_get_birthday_last_year, lambda x:x)
1766
1767
1768
1769
1771 cmd = 'select * from clin.v_most_recent_encounters where pk_patient=%s'
1772 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self._payload[self._idx['pk_identity']]]}])
1773 if len(rows) > 0:
1774 return rows[0]
1775 else:
1776 return None
1777
1780
1781 messages = property(get_messages, lambda x:x)
1782
1785
1786 overdue_messages = property(_get_overdue_messages, lambda x:x)
1787
1788
1791
1792
1798
1799 dynamic_hints = property(_get_dynamic_hints, lambda x:x)
1800
1801
1804
1805 suppressed_hints = property(_get_suppressed_hints, lambda x:x)
1806
1807
1809 if self._payload[self._idx['pk_primary_provider']] is None:
1810 return None
1811 cmd = "SELECT * FROM dem.v_all_persons WHERE pk_identity = (SELECT pk_identity FROM dem.v_staff WHERE pk_staff = %(pk_staff)s)"
1812 args = {'pk_staff': self._payload[self._idx['pk_primary_provider']]}
1813 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1814 if len(rows) == 0:
1815 return None
1816 return cPerson(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_identity'})
1817
1818 primary_provider_identity = property(_get_primary_provider_identity, lambda x:x)
1819
1820
1822 if self._payload[self._idx['pk_primary_provider']] is None:
1823 return None
1824 from Gnumed.business import gmStaff
1825 return gmStaff.cStaff(aPK_obj = self._payload[self._idx['pk_primary_provider']])
1826
1827 primary_provider = property(_get_primary_provider, lambda x:x)
1828
1829
1830
1831
1833 """Format patient demographics into patient specific path name fragment."""
1834
1835 return gmTools.fname_sanitize('%s-%s-%s' % (
1836 self._payload[self._idx['lastnames']],
1837 self._payload[self._idx['firstnames']],
1838 self.get_formatted_dob(format = '%Y-%m-%d')
1839 ))
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863 subdir_name = property(get_subdir_name, lambda x:x)
1864
1865
1867 cmd = 'SELECT 1 FROM clin.patient WHERE fk_identity = %(pk_pat)s'
1868 args = {'pk_pat': pk_identity}
1869 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1870 if len(rows) == 0:
1871 return False
1872 return True
1873
1874
1876 cmd = """
1877 INSERT INTO clin.patient (fk_identity)
1878 SELECT %(pk_ident)s WHERE NOT EXISTS (
1879 SELECT 1 FROM clin.patient c_p WHERE fk_identity = %(pk_ident)s
1880 )"""
1881 args = {'pk_ident': pk_identity}
1882 queries = [{'cmd': cmd, 'args': args}]
1883 gmPG2.run_rw_queries(queries = queries)
1884 return True
1885
1886
1887
1888
1889 _yield = lambda x:x
1890
1892 if not callable(yielder):
1893 raise TypeError('yielder <%s> is not callable' % yielder)
1894 global _yield
1895 _yield = yielder
1896 _log.debug('setting yielder to <%s>', yielder)
1897
1898
1900 """Represents a person which is a patient.
1901
1902 - a specializing subclass of cPerson turning it into a patient
1903 - its use is to cache subobjects like EMR and document folder
1904 """
1905 - def __init__(self, aPK_obj=None, row=None):
1906 cPerson.__init__(self, aPK_obj = aPK_obj, row = row)
1907 self.__emr_access_lock = threading.Lock()
1908 self.__emr = None
1909 self.__doc_folder = None
1910
1911
1913 """Do cleanups before dying.
1914
1915 - note that this may be called in a thread
1916 """
1917 if self.__emr is not None:
1918 self.__emr.cleanup()
1919 if self.__doc_folder is not None:
1920 self.__doc_folder.cleanup()
1921 cPerson.cleanup(self)
1922
1923
1928
1929
1931 _log.debug('accessing EMR for identity [%s], thread [%s]', self._payload[self._idx['pk_identity']], threading.get_ident())
1932
1933
1934 if self.__emr is not None:
1935 return self.__emr
1936
1937 stack_logged = False
1938 got_lock = self.__emr_access_lock.acquire(False)
1939 if not got_lock:
1940
1941 call_stack = inspect.stack()
1942 call_stack.reverse()
1943 for idx in range(1, len(call_stack)):
1944 caller = call_stack[idx]
1945 _log.debug('%s[%s] @ [%s] in [%s]', ' '* idx, caller[3], caller[2], caller[1])
1946 del call_stack
1947 stack_logged = True
1948
1949 for idx in range(500):
1950 _yield()
1951 time.sleep(0.1)
1952 _yield()
1953 got_lock = self.__emr_access_lock.acquire(False)
1954 if got_lock:
1955 break
1956 if not got_lock:
1957 _log.error('still failed to acquire EMR access lock, aborting (thread [%s])', threading.get_ident())
1958 self.__emr_access_lock.release()
1959 raise AttributeError('cannot lock access to EMR for identity [%s]' % self._payload[self._idx['pk_identity']])
1960
1961 _log.debug('pulling chart for identity [%s], thread [%s]', self._payload[self._idx['pk_identity']], threading.get_ident())
1962 if not stack_logged:
1963
1964 call_stack = inspect.stack()
1965 call_stack.reverse()
1966 for idx in range(1, len(call_stack)):
1967 caller = call_stack[idx]
1968 _log.debug('%s[%s] @ [%s] in [%s]', ' '* idx, caller[3], caller[2], caller[1])
1969 del call_stack
1970 stack_logged = True
1971
1972 self.is_patient = True
1973 from Gnumed.business import gmClinicalRecord
1974 emr = gmClinicalRecord.cClinicalRecord(aPKey = self._payload[self._idx['pk_identity']])
1975
1976 _log.debug('returning EMR for identity [%s], thread [%s]', self._payload[self._idx['pk_identity']], threading.get_ident())
1977 self.__emr = emr
1978 self.__emr_access_lock.release()
1979 return self.__emr
1980
1981 emr = property(get_emr, lambda x:x)
1982
1983
1985 if self.__doc_folder is None:
1986 self.__doc_folder = cDocumentFolder(aPKey = self._payload[self._idx['pk_identity']])
1987 return self.__doc_folder
1988
1989 document_folder = property(get_document_folder, lambda x:x)
1990
1991
1993 """Patient Borg to hold the currently active patient.
1994
1995 There may be many instances of this but they all share state.
1996
1997 The underlying dem.identity row must have .deleted set to FALSE.
1998
1999 The sequence of events when changing the active patient:
2000
2001 1) Registered callbacks are run.
2002 Those are run synchronously. If a callback
2003 returns False or throws an exception the
2004 patient switch is aborted. Callback code
2005 can rely on the patient still being active
2006 and to not go away until it returns. It
2007 is not passed any arguments and must return
2008 False or True.
2009
2010 2) Signal "pre_patient_unselection" is sent.
2011 This does not wait for nor check results.
2012 The keyword pk_identity contains the
2013 PK of the person being switched away
2014 from.
2015
2016 3) the current patient is unset (gmNull.cNull)
2017
2018 4) Signal "current_patient_unset" is sent
2019 At this point resetting GUI fields to
2020 empty should be done. The active patient
2021 is not there anymore.
2022
2023 This does not wait for nor check results.
2024
2025 5) The current patient is set to the new value.
2026 The new patient can also remain gmNull.cNull
2027 in case the calling code explicitely unset
2028 the current patient.
2029
2030 6) Signal "post_patient_selection" is sent.
2031 Code listening to this signal can
2032 assume that the new patient is
2033 already active.
2034 """
2035 - def __init__(self, patient=None, forced_reload=False):
2036 """Change or get currently active patient.
2037
2038 patient:
2039 * None: get currently active patient
2040 * -1: unset currently active patient
2041 * cPatient instance: set active patient if possible
2042 """
2043
2044 try:
2045 self.patient
2046 except AttributeError:
2047 self.patient = gmNull.cNull()
2048 self.__register_interests()
2049
2050
2051
2052 self.__lock_depth = 0
2053
2054 self.__callbacks_before_switching_away_from_patient = []
2055
2056
2057 if patient is None:
2058 return None
2059
2060
2061 if self.locked:
2062 _log.error('patient [%s] is locked, cannot change to [%s]' % (self.patient['pk_identity'], patient))
2063 return None
2064
2065
2066 if patient == -1:
2067 _log.debug('explicitly unsetting current patient')
2068 if not self.__run_callbacks_before_switching_away_from_patient():
2069 _log.error('not unsetting current patient, at least one pre-change callback failed')
2070 return None
2071 self.__send_pre_unselection_notification()
2072 self.patient.cleanup()
2073 self.patient = gmNull.cNull()
2074 self.__send_unselection_notification()
2075
2076 time.sleep(0.5)
2077 self.__send_selection_notification()
2078 return None
2079
2080
2081 if not isinstance(patient, cPatient):
2082 _log.error('cannot set active patient to [%s], must be either None, -1 or cPatient instance' % str(patient))
2083 raise TypeError('gmPerson.gmCurrentPatient.__init__(): <patient> must be None, -1 or cPatient instance but is: %s' % str(patient))
2084
2085
2086 if (self.patient['pk_identity'] == patient['pk_identity']) and not forced_reload:
2087 return None
2088
2089 if patient['is_deleted']:
2090 _log.error('cannot set active patient to disabled dem.identity row: %s', patient)
2091 raise ValueError('gmPerson.gmCurrentPatient.__init__(): <patient> is disabled: %s' % patient)
2092
2093
2094 _log.info('patient change [%s] -> [%s] requested', self.patient['pk_identity'], patient['pk_identity'])
2095
2096 if not self.__run_callbacks_before_switching_away_from_patient():
2097 _log.error('not changing current patient, at least one pre-change callback failed')
2098 return None
2099
2100
2101 self.__send_pre_unselection_notification()
2102 self.patient.cleanup()
2103 self.patient = gmNull.cNull()
2104 self.__send_unselection_notification()
2105
2106 time.sleep(0.5)
2107 self.patient = patient
2108
2109
2110 self.patient.emr
2111 self.__send_selection_notification()
2112
2113 return None
2114
2115
2118
2119
2121
2122 if isinstance(self.patient, gmNull.cNull):
2123 return True
2124
2125
2126 if kwds['table'] not in ['dem.identity', 'dem.names']:
2127 return True
2128
2129
2130 if int(kwds['pk_identity']) != self.patient.ID:
2131 return True
2132
2133 if kwds['table'] == 'dem.identity':
2134
2135 if kwds['operation'] != 'UPDATE':
2136 return True
2137
2138 self.patient.refetch_payload()
2139 return True
2140
2141
2142
2143
2145
2146
2147
2148
2149
2150 if not callable(callback):
2151 raise TypeError('callback [%s] not callable' % callback)
2152
2153 self.__callbacks_before_switching_away_from_patient.append(callback)
2154
2155
2158
2159 connected = property(_get_connected, lambda x:x)
2160
2161
2163 return (self.__lock_depth > 0)
2164
2166 if locked:
2167 self.__lock_depth = self.__lock_depth + 1
2168 gmDispatcher.send(signal = 'patient_locked', sender = self.__class__.__name__)
2169 else:
2170 if self.__lock_depth == 0:
2171 _log.error('lock/unlock imbalance, tried to refcount lock depth below 0')
2172 return
2173 else:
2174 self.__lock_depth = self.__lock_depth - 1
2175 gmDispatcher.send(signal = 'patient_unlocked', sender = self.__class__.__name__)
2176
2177 locked = property(_get_locked, _set_locked)
2178
2179
2181 _log.info('forced patient unlock at lock depth [%s]' % self.__lock_depth)
2182 self.__lock_depth = 0
2183 gmDispatcher.send(signal = 'patient_unlocked', sender = self.__class__.__name__)
2184
2185
2186
2187
2189 if isinstance(self.patient, gmNull.cNull):
2190 return True
2191
2192 for call_back in self.__callbacks_before_switching_away_from_patient:
2193 try:
2194 successful = call_back()
2195 except:
2196 _log.exception('callback [%s] failed', call_back)
2197 print("*** pre-change callback failed ***")
2198 print(type(call_back))
2199 print(call_back)
2200 return False
2201
2202 if not successful:
2203 _log.error('callback [%s] returned False', call_back)
2204 return False
2205
2206 return True
2207
2208
2210 """Sends signal when current patient is about to be unset.
2211
2212 This does NOT wait for signal handlers to complete.
2213 """
2214 kwargs = {
2215 'signal': 'pre_patient_unselection',
2216 'sender': self.__class__.__name__,
2217 'pk_identity': self.patient['pk_identity']
2218 }
2219 gmDispatcher.send(**kwargs)
2220
2221
2223 """Sends signal when the previously active patient has
2224 been unset during a change of active patient.
2225
2226 This is the time to initialize GUI fields to empty values.
2227
2228 This does NOT wait for signal handlers to complete.
2229 """
2230 kwargs = {
2231 'signal': 'current_patient_unset',
2232 'sender': self.__class__.__name__
2233 }
2234 gmDispatcher.send(**kwargs)
2235
2236
2238 """Sends signal when another patient has actually been made active."""
2239 kwargs = {
2240 'signal': 'post_patient_selection',
2241 'sender': self.__class__.__name__,
2242 'pk_identity': self.patient['pk_identity']
2243 }
2244 gmDispatcher.send(**kwargs)
2245
2246
2247
2248
2250
2251
2252
2253
2254
2255
2256 if attribute == 'patient':
2257 raise AttributeError
2258 if isinstance(self.patient, gmNull.cNull):
2259 _log.error("[%s]: cannot getattr(%s, '%s'), patient attribute not connected to a patient", self, self.patient, attribute)
2260 raise AttributeError("[%s]: cannot getattr(%s, '%s'), patient attribute not connected to a patient" % (self, self.patient, attribute))
2261 return getattr(self.patient, attribute)
2262
2263
2264
2265
2267 """Return any attribute if known how to retrieve it by proxy.
2268 """
2269 return self.patient[attribute]
2270
2271
2274
2275
2276
2277
2280 gmMatchProvider.cMatchProvider_SQL2.__init__(
2281 self,
2282 queries = [
2283 """SELECT
2284 pk_staff AS data,
2285 short_alias || ' (' || coalesce(title, '') || ' ' || firstnames || ' ' || lastnames || ')' AS list_label,
2286 short_alias || ' (' || coalesce(title, '') || ' ' || firstnames || ' ' || lastnames || ')' AS field_label
2287 FROM dem.v_staff
2288 WHERE
2289 is_active AND (
2290 short_alias %(fragment_condition)s OR
2291 firstnames %(fragment_condition)s OR
2292 lastnames %(fragment_condition)s OR
2293 db_user %(fragment_condition)s
2294 )
2295 """
2296 ]
2297 )
2298 self.setThresholds(1, 2, 3)
2299
2300
2301
2302
2303 -def create_name(pk_person, firstnames, lastnames, active=False):
2304 queries = [{
2305 'cmd': "select dem.add_name(%s, %s, %s, %s)",
2306 'args': [pk_person, firstnames, lastnames, active]
2307 }]
2308 rows, idx = gmPG2.run_rw_queries(queries=queries, return_data=True)
2309 name = cPersonName(aPK_obj = rows[0][0])
2310 return name
2311
2312
2313 -def create_identity(gender=None, dob=None, lastnames=None, firstnames=None):
2314
2315 cmd1 = """INSERT INTO dem.identity (gender, dob) VALUES (%s, %s)"""
2316 cmd2 = """
2317 INSERT INTO dem.names (
2318 id_identity, lastnames, firstnames
2319 ) VALUES (
2320 currval('dem.identity_pk_seq'), coalesce(%s, 'xxxDEFAULTxxx'), coalesce(%s, 'xxxDEFAULTxxx')
2321 ) RETURNING id_identity"""
2322
2323 rows, idx = gmPG2.run_rw_queries (
2324 queries = [
2325 {'cmd': cmd1, 'args': [gender, dob]},
2326 {'cmd': cmd2, 'args': [lastnames, firstnames]}
2327
2328 ],
2329 return_data = True
2330 )
2331 ident = cPerson(aPK_obj = rows[0][0])
2332 gmHooks.run_hook_script(hook = 'post_person_creation')
2333 return ident
2334
2335
2337 _log.info('disabling identity [%s]', pk_identity)
2338 cmd = "UPDATE dem.identity SET deleted = true WHERE pk = %(pk)s"
2339 args = {'pk': pk_identity}
2340 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2341 return True
2342
2343
2351
2352
2354 cmd = 'SELECT EXISTS(SELECT 1 FROM dem.identity where pk = %(pk)s)'
2355 args = {'pk': pk_identity}
2356 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2357 return rows[0][0]
2358
2359
2361 """Set active patient.
2362
2363 If patient is -1 the active patient will be UNset.
2364 """
2365 if isinstance(patient, gmCurrentPatient):
2366 return True
2367
2368 if isinstance(patient, cPatient):
2369 pat = patient
2370 elif isinstance(patient, cPerson):
2371 pat = pat.as_patient
2372 elif patient == -1:
2373 pat = patient
2374 else:
2375
2376 success, pk = gmTools.input2int(initial = patient, minval = 1)
2377 if not success:
2378 raise ValueError('<patient> must be either -1, >0, or a cPatient, cPerson or gmCurrentPatient instance, is: %s' % patient)
2379
2380 try:
2381 pat = cPatient(aPK_obj = pk)
2382 except:
2383 _log.exception('identity [%s] not found' % patient)
2384 return False
2385
2386
2387 try:
2388 gmCurrentPatient(patient = pat, forced_reload = forced_reload)
2389 except:
2390 _log.exception('error changing active patient to [%s]' % patient)
2391 return False
2392
2393 return True
2394
2395
2396
2397
2409
2410
2411 map_gender2mf = {
2412 'm': 'm',
2413 'f': 'f',
2414 'tf': 'f',
2415 'tm': 'm',
2416 'h': 'mf'
2417 }
2418
2419
2420
2421 map_gender2vcard = {
2422 'm': 'M',
2423 'f': 'F',
2424 'tf': 'F',
2425 'tm': 'M',
2426 'h': 'O',
2427 None: 'U'
2428 }
2429
2430
2431
2432 map_gender2symbol = {
2433 'm': '\u2642',
2434 'f': '\u2640',
2435 'tf': '\u26A5\u2640',
2436
2437 'tm': '\u26A5\u2642',
2438
2439 'h': '\u26A5',
2440
2441 None: '?\u26A5?'
2442 }
2443
2465
2488
2490 """Try getting the gender for the given first name."""
2491
2492 if firstnames is None:
2493 return None
2494
2495 rows, idx = gmPG2.run_ro_queries(queries = [{
2496 'cmd': "SELECT gender FROM dem.name_gender_map WHERE name ILIKE %(fn)s LIMIT 1",
2497 'args': {'fn': firstnames}
2498 }])
2499
2500 if len(rows) == 0:
2501 return None
2502
2503 return rows[0][0]
2504
2506 cmd = 'SELECT pk FROM dem.identity'
2507 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False)
2508 return [ r[0] for r in rows ]
2509
2510
2512 return [ cPerson(aPK_obj = pk) for pk in pks ]
2513
2517
2521
2522
2523
2524
2525 if __name__ == '__main__':
2526
2527 if len(sys.argv) == 1:
2528 sys.exit()
2529
2530 if sys.argv[1] != 'test':
2531 sys.exit()
2532
2533 import datetime
2534
2535 gmI18N.activate_locale()
2536 gmI18N.install_domain()
2537 gmDateTime.init()
2538
2539
2560
2562 dto = cDTO_person()
2563 dto.firstnames = 'Sepp'
2564 dto.lastnames = 'Herberger'
2565 dto.gender = 'male'
2566 dto.dob = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone)
2567 print(dto)
2568
2569 print(dto['firstnames'])
2570 print(dto['lastnames'])
2571 print(dto['gender'])
2572 print(dto['dob'])
2573
2574 for key in dto.keys():
2575 print(key)
2576
2578
2579 print('\n\nCreating identity...')
2580 new_identity = create_identity(gender='m', dob='2005-01-01', lastnames='test lastnames', firstnames='test firstnames')
2581 print('Identity created: %s' % new_identity)
2582
2583 print('\nSetting title and gender...')
2584 new_identity['title'] = 'test title';
2585 new_identity['gender'] = 'f';
2586 new_identity.save_payload()
2587 print('Refetching identity from db: %s' % cPerson(aPK_obj=new_identity['pk_identity']))
2588
2589 print('\nGetting all names...')
2590 for a_name in new_identity.get_names():
2591 print(a_name)
2592 print('Active name: %s' % (new_identity.get_active_name()))
2593 print('Setting nickname...')
2594 new_identity.set_nickname(nickname='test nickname')
2595 print('Refetching all names...')
2596 for a_name in new_identity.get_names():
2597 print(a_name)
2598 print('Active name: %s' % (new_identity.get_active_name()))
2599
2600 print('\nIdentity occupations: %s' % new_identity['occupations'])
2601 print('Creating identity occupation...')
2602 new_identity.link_occupation('test occupation')
2603 print('Identity occupations: %s' % new_identity['occupations'])
2604
2605 print('\nIdentity addresses: %s' % new_identity.get_addresses())
2606 print('Creating identity address...')
2607
2608 new_identity.link_address (
2609 number = 'test 1234',
2610 street = 'test street',
2611 postcode = 'test postcode',
2612 urb = 'test urb',
2613 region_code = 'SN',
2614 country_code = 'DE'
2615 )
2616 print('Identity addresses: %s' % new_identity.get_addresses())
2617
2618 print('\nIdentity communications: %s' % new_identity.get_comm_channels())
2619 print('Creating identity communication...')
2620 new_identity.link_comm_channel('homephone', '1234566')
2621 print('Identity communications: %s' % new_identity.get_comm_channels())
2622
2628
2630 genders, idx = get_gender_list()
2631 print("\n\nRetrieving gender enum (tag, label, weight):")
2632 for gender in genders:
2633 print("%s, %s, %s" % (gender[idx['tag']], gender[idx['l10n_label']], gender[idx['sort_weight']]))
2634
2640
2644
2645
2649
2650
2654
2655
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675 test_ext_id()
2676 test_current_patient()
2677
2678
2679