1 """GNUmed demographics object.
2
3 This is a patient object intended to let a useful client-side
4 API crystallize from actual use in true XP fashion.
5
6 license: GPL
7 """
8
9
10
11 __version__ = "$Revision: 1.103 $"
12 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>, I.Haywood <ihaywood@gnu.org>"
13
14
15 import sys, os.path, time, string, logging
16
17
18
19 import mx.DateTime as mxDT
20
21
22
23 if __name__ == '__main__':
24 sys.path.insert(0, '../../')
25 from Gnumed.pycommon import gmDispatcher, gmBusinessDBObject, gmPG2, gmTools
26 from Gnumed.business import gmMedDoc
27
28
29 _log = logging.getLogger('gm.business')
30 _log.info(__version__)
31
32
34
35 args = {'prov': province}
36
37 queries = []
38 if delete_urbs:
39 queries.append ({
40 'cmd': u"""
41 delete from dem.urb du
42 where
43 du.id_state = %(prov)s
44 and
45 not exists (select 1 from dem.street ds where ds.id_urb = du.id)""",
46 'args': args
47 })
48
49 queries.append ({
50 'cmd': u"""
51 delete from dem.state ds
52 where
53 ds.id = %(prov)s
54 and
55 not exists (select 1 from dem.urb du where du.id_state = ds.id)""",
56 'args': args
57 })
58
59 gmPG2.run_rw_queries(queries = queries)
60
61 return True
62
64
65 args = {'code': code, 'country': country, 'name': name}
66
67 cmd = u"""SELECT EXISTS (SELECT 1 FROM dem.state WHERE name = %(name)s)"""
68 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
69
70 if rows[0][0]:
71 return
72
73 cmd = u"""
74 INSERT INTO dem.state (
75 code, country, name
76 ) VALUES (
77 %(code)s, %(country)s, %(name)s
78 )"""
79 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
80
82 cmd = u"""
83 select
84 l10n_state, l10n_country, state, code_state, code_country, pk_state, country_deprecated
85 from dem.v_state
86 order by l10n_country, l10n_state"""
87 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
88 return rows
89
90
91
92 -class cAddress(gmBusinessDBObject.cBusinessDBObject):
93 """A class representing an address as an entity in itself.
94
95 We consider addresses to be self-complete "labels" for locations.
96 It does not depend on any people potentially living there. Thus
97 an address can get attached to as many people as we want to
98 signify that that is their place of residence/work/...
99
100 This class acts on the address as an entity. Therefore it can
101 modify the address fields. Think carefully about *modifying*
102 addresses attached to people, though. Most times when you think
103 person.modify_address() what you *really* want is as sequence of
104 person.unlink_address(old) and person.link_address(new).
105
106 Modifying an address may or may not be the proper thing to do as
107 it will transparently modify the address for *all* the people to
108 whom it is attached. In many cases you will want to create a *new*
109 address and link it to a person instead of the old address.
110 """
111 _cmd_fetch_payload = u"select * from dem.v_address where pk_address=%s"
112 _cmds_store_payload = [
113 u"""update dem.address set
114 aux_street = %(notes_street)s,
115 subunit = %(subunit)s,
116 addendum = %(notes_subunit)s,
117 lat_lon = %(lat_lon_street)s
118 where id=%(pk_address)s and xmin=%(xmin_address)s""",
119 u"select xmin as xmin_address from dem.address where id=%(pk_address)s"
120 ]
121 _updatable_fields = ['notes_street', 'subunit', 'notes_subunit', 'lat_lon_address']
122
123 -def address_exists(country=None, state=None, urb=None, suburb=None, postcode=None, street=None, number=None, subunit=None, notes_street=None, notes_subunit=None):
124
125 where_parts = [u"""
126 code_country = %(country)s and
127 code_state = %(state)s and
128 urb = %(urb)s and
129 postcode = %(postcode)s and
130 street = %(street)s and
131 number = %(number)s"""
132 ]
133
134 if suburb is None:
135 where_parts.append(u"suburb is %(suburb)s")
136 else:
137 where_parts.append(u"suburb = %(suburb)s")
138
139 if notes_street is None:
140 where_parts.append(u"notes_street is %(notes_street)s")
141 else:
142 where_parts.append(u"notes_street = %(notes_street)s")
143
144 if subunit is None:
145 where_parts.append(u"subunit is %(subunit)s")
146 else:
147 where_parts.append(u"subunit = %(subunit)s")
148
149 if notes_subunit is None:
150 where_parts.append(u"notes_subunit is %(notes_subunit)s")
151 else:
152 where_parts.append(u"notes_subunit = %(notes_subunit)s")
153
154 cmd = u"select pk_address from dem.v_address where %s" % u" and ".join(where_parts)
155 data = {
156 'country': country,
157 'state': state,
158 'urb': urb,
159 'suburb': suburb,
160 'postcode': postcode,
161 'street': street,
162 'notes_street': notes_street,
163 'number': number,
164 'subunit': subunit,
165 'notes_subunit': notes_subunit
166 }
167
168 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': data}])
169
170 if len(rows) == 0:
171 return None
172 return rows[0][0]
173
174 -def create_address(country=None, state=None, urb=None, suburb=None, postcode=None, street=None, number=None, subunit=None):
175
176 if suburb is not None:
177 suburb = gmTools.none_if(suburb.strip(), u'')
178
179 pk_address = address_exists (
180 country = country,
181 state = state,
182 urb = urb,
183 suburb = suburb,
184 postcode = postcode,
185 street = street,
186 number = number,
187 subunit = subunit
188 )
189 if pk_address is not None:
190 return cAddress(aPK_obj=pk_address)
191
192 cmd = u"""
193 select dem.create_address (
194 %(number)s,
195 %(street)s,
196 %(postcode)s,
197 %(urb)s,
198 %(state)s,
199 %(country)s,
200 %(subunit)s
201 )"""
202 args = {
203 'number': number,
204 'street': street,
205 'postcode': postcode,
206 'urb': urb,
207 'state': state,
208 'country': country,
209 'subunit': subunit
210 }
211 queries = [{'cmd': cmd, 'args': args}]
212
213 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
214 adr = cAddress(aPK_obj=rows[0][0])
215
216 if suburb is not None:
217 queries = [{
218
219 'cmd': u"update dem.street set suburb = %(suburb)s where id=%(pk_street)s and suburb is Null",
220 'args': {'suburb': suburb, 'pk_street': adr['pk_street']}
221 }]
222 rows, idx = gmPG2.run_rw_queries(queries = queries)
223
224 return adr
225
227 cmd = u"delete from dem.address where id=%s"
228 rows, idx = gmPG2.run_rw_queries(queries=[{'cmd': cmd, 'args': [address['pk_address']]}])
229 return True
230
232 cmd = u'select id as pk, name, _(name) as l10n_name from dem.address_type'
233 rows, idx = gmPG2.run_rw_queries(queries=[{'cmd': cmd}])
234 return rows
235
237
238 _cmd_fetch_payload = u"select * from dem.v_pat_addresses where pk_address=%s"
239 _cmds_store_payload = [
240 u"""update dem.lnk_person_org_address set id_type=%(pk_address_type)s
241 where id=%(pk_lnk_person_org_address)s and xmin=%(xmin_lnk_person_org_address)s""",
242 u"""select xmin from dem.lnk_person_org_address where id=%(pk_lnk_person_org_address)s"""
243 ]
244 _updatable_fields = ['pk_address_type']
245
248
249
250
252
253 _cmd_fetch_payload = u"select * from dem.v_person_comms where pk_lnk_identity2comm = %s"
254 _cmds_store_payload = [
255 u"""update dem.lnk_identity2comm set
256 fk_address = %(pk_address)s,
257 fk_type = dem.create_comm_type(%(comm_type)s),
258 url = %(url)s,
259 is_confidential = %(is_confidential)s
260 where pk = %(pk_lnk_identity2comm)s and xmin = %(xmin_lnk_identity2comm)s
261 """,
262 u"select xmin as xmin_lnk_identity2comm from dem.lnk_identity2comm where pk = %(pk_lnk_identity2comm)s"
263 ]
264 _updatable_fields = ['pk_address', 'url', 'comm_type', 'is_confidential']
265
266 -def create_comm_channel(comm_medium=None, url=None, is_confidential=False, pk_channel_type=None, pk_identity=None):
267 """Create a communications channel for a patient."""
268
269 if url is None:
270 return None
271
272
273 args = {'pat': pk_identity, 'url': url, 'secret': is_confidential}
274
275 if pk_channel_type is None:
276 args['type'] = comm_medium
277 cmd = u"""insert into dem.lnk_identity2comm (
278 fk_identity,
279 url,
280 fk_type,
281 is_confidential
282 ) values (
283 %(pat)s,
284 %(url)s,
285 dem.create_comm_type(%(type)s),
286 %(secret)s
287 )"""
288 else:
289 args['type'] = pk_channel_type
290 cmd = u"""insert into dem.lnk_identity2comm (
291 fk_identity,
292 url,
293 fk_type,
294 is_confidential
295 ) values (
296 %(pat)s,
297 %(url)s,
298 %(type)s,
299 %(secret)s
300 )"""
301
302 rows, idx = gmPG2.run_rw_queries (
303 queries = [
304 {'cmd': cmd, 'args': args},
305 {'cmd': u"select * from dem.v_person_comms where pk_lnk_identity2comm = currval(pg_get_serial_sequence('dem.lnk_identity2comm', 'pk'))"}
306 ],
307 return_data = True,
308 get_col_idx = True
309 )
310
311 return cCommChannel(row = {'pk_field': 'pk_lnk_identity2comm', 'data': rows[0], 'idx': idx})
312
314 cmd = u"delete from dem.lnk_identity2comm where pk = %(pk)s and fk_identity = %(pat)s"
315 args = {'pk': pk, 'pat': pk_patient}
316 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
317
318 __comm_channel_types = None
319
327
328
329
330 -class cOrg (gmBusinessDBObject.cBusinessDBObject):
331 """
332 Organisations
333
334 This is also the common ancestor of cIdentity, self._table is used to
335 hide the difference.
336 The aim is to be able to sanely write code which doesn't care whether
337 its talking to an organisation or an individual"""
338 _table = "org"
339
340 _cmd_fetch_payload = "select *, xmin from dem.org where id=%s"
341 _cmds_lock_rows_for_update = ["select 1 from dem.org where id=%(id)s and xmin=%(xmin)s"]
342 _cmds_store_payload = [
343 """update dem.org set
344 description=%(description)s,
345 id_category=(select id from dem.org_category where description=%(occupation)s)
346 where id=%(id)s""",
347 "select xmin from dem.org where id=%(id)s"
348 ]
349 _updatable_fields = ["description", "occupation"]
350 _service = 'personalia'
351
354
356 if not self.__cache.has_key ('addresses'):
357 self['addresses']
358 if not self.__cache.has_key ('comms'):
359 self['comms']
360 return self.__cache
361
363 """
364 Returns a list of (address dict, cIdentity) tuples
365 """
366 cmd = """select
367 vba.id,
368 vba.number,
369 vba.addendum,
370 vba.street,
371 vba.urb,
372 vba.postcode,
373 at.name,
374 lpoa.id_type,
375 vbp.pk_identity,
376 title,
377 firstnames,
378 lastnames,
379 dob,
380 cob,
381 gender,
382 pupic,
383 pk_marital_status,
384 marital_status,
385 karyotype,
386 xmin_identity,
387 preferred
388 from
389 dem.v_basic_address vba,
390 dem.lnk_person_org_address lpoa,
391 dem.address_type at,
392 dem.v_basic_person vbp
393 where
394 lpoa.id_address = vba.id
395 and lpoa.id_type = at.id
396 and lpoa.id_identity = vbp.pk_identity
397 and lpoa.id_org = %%s
398 """
399
400 rows, idx = gmPG.run_ro_query('personalia', cmd, 1, self.getId ())
401 if rows is None:
402 return []
403 elif len(rows) == 0:
404 return []
405 else:
406 return [({'pk':i[0], 'number':i[1], 'addendum':i[2], 'street':i[3], 'city':i[4], 'postcode':i[5], 'type':i[6], 'id_type':i[7]}, cIdentity (row = {'data':i[8:], 'id':idx[8:], 'pk_field':'id'})) for i in rows]
407
409 """
410 Binds a person to this organisation at this address.
411 person is a cIdentity object
412 address is a dict of {'number', 'street', 'addendum', 'city', 'postcode', 'type'}
413 type is one of the IDs returned by getAddressTypes
414 """
415 cmd = "insert into dem.lnk_person_org_address (id_type, id_address, id_org, id_identity) values (%(type)s, dem.create_address (%(number)s, %(addendum)s, %(street)s, %(city)s, %(postcode)s), %(org_id)s, %(pk_identity)s)"
416 address['pk_identity'] = person['pk_identity']
417 address['org_id'] = self.getId()
418 if not id_addr:
419 return (False, None)
420 return gmPG.run_commit2 ('personalia', [(cmd, [address])])
421
423 cmd = "delete from dem.lnk_person_org_address where id_org = %s and id_identity = %s"
424 return gmPG.run_commit2 ('personalia', [(cmd, [self.getId(), person.ID])])
425
427 """
428 Hide the difference between org.id and v_basic_person.pk_identity
429 """
430 return self['id']
431
433 """
434 wrap mx.DateTime brokenness
435 Returns 9-tuple for use with pyhon time functions
436 """
437 return [ int(x) for x in str(mx).split(' ')[0].split('-') ] + [0,0,0, 0,0,0]
438
440 """Gets a dict matching address types to their ID"""
441 row_list = gmPG.run_ro_query('personalia', "select name, id from dem.address_type")
442 if row_list is None:
443 return {}
444 if len(row_list) == 0:
445 return {}
446 return dict (row_list)
447
449 """Gets a dictionary matching marital status types to their internal ID"""
450 row_list = gmPG.run_ro_query('personalia', "select name, pk from dem.marital_status")
451 if row_list is None:
452 return {}
453 if len(row_list) == 0:
454 return {}
455 return dict(row_list)
456
457 -def getExtIDTypes (context = 'p'):
458 """Gets dictionary mapping ext ID names to internal code from the backend for the given context
459 """
460
461 rl = gmPG.run_ro_query('personalia', "select name, pk from dem.enum_ext_id_types where context = %s", None, context)
462 if rl is None:
463 return {}
464 return dict (rl)
465
467 """Gets a dictionary of relationship types to internal id"""
468 row_list = gmPG.run_ro_query('personalia', "select description, id from dem.relation_types")
469 if row_list is None:
470 return None
471 if len (row_list) == 0:
472 return None
473 return dict(row_list)
474
475
477 cmd = """
478 select
479 dem.state.name,
480 dem.urb.postcode
481 from
482 dem.urb,
483 dem.state
484 where
485 dem.urb.id = %s and
486 dem.urb.id_state = dem.state.id"""
487 row_list = gmPG.run_ro_query('personalia', cmd, None, id_urb)
488 if not row_list:
489 return None
490 else:
491 return (row_list[0][0], row_list[0][1])
492
494 cmd = """
495 select
496 dem.state.name,
497 coalesce (dem.street.postcode, dem.urb.postcode),
498 dem.urb.name
499 from
500 dem.urb,
501 dem.state,
502 dem.street
503 where
504 dem.street.id = %s and
505 dem.street.id_urb = dem.urb.id and
506 dem.urb.id_state = dem.state.id
507 """
508 row_list = gmPG.run_ro_query('personalia', cmd, None, id_street)
509 if not row_list:
510 return None
511 else:
512 return (row_list[0][0], row_list[0][1], row_list[0][2])
513
515 row_list = gmPG.run_ro_query('personalia', "select name from dem.country where code = %s", None, country_code)
516 if not row_list:
517 return None
518 else:
519 return row_list[0][0]
520
522 row_list = gmPG.run_ro_query ('personalia', """
523 select
524 dem.urb.postcode,
525 dem.state.code,
526 dem.state.name,
527 dem.country.code,
528 dem.country.name
529 from
530 dem.urb,
531 dem.state,
532 dem.country
533 where
534 dem.urb.name = %s and
535 dem.urb.id_state = dem.state.id and
536 dem.state.country = dem.country.code""", None, town)
537 if not row_list:
538 return (None, None, None, None, None)
539 else:
540 return tuple (row_list[0])
541
542
543
545 print "received post_patient_selection notification"
546 print kwargs['kwds']
547
548
549
550
551
552 if __name__ == "__main__":
553
554 import random
555
557 exists = address_exists (
558 country ='Germany',
559 state ='Sachsen',
560 urb ='Leipzig',
561 suburb ='Sellerhausen',
562 postcode ='04318',
563 street = u'Cunnersdorfer Strasse',
564 number = '11',
565 notes_subunit = '4.Stock rechts'
566 )
567 if exists is None:
568 print "address does not exist"
569 else:
570 print "address exists, primary key:", exists
571
573 address = create_address (
574 country ='DE',
575 state ='SN',
576 urb ='Leipzig',
577 suburb ='Sellerhausen',
578 postcode ='04318',
579 street = u'Cunnersdorfer Strasse',
580 number = '11'
581
582 )
583 print "created existing address"
584 print address
585
586 su = str(random.random())
587
588 address = create_address (
589 country ='DE',
590 state = 'SN',
591 urb ='Leipzig',
592 suburb ='Sellerhausen',
593 postcode ='04318',
594 street = u'Cunnersdorfer Strasse',
595 number = '11',
596
597 subunit = su
598 )
599 print "created new address with subunit", su
600 print address
601 print "deleted address:", delete_address(address)
602
603
604 try:
605 gmPG2.get_connection()
606
607 test_address_exists()
608 test_create_address()
609 except:
610 _log.exception('test suite failed')
611 raise
612
613 sys.exit()
614
615 gmDispatcher.connect(_post_patient_selection, 'post_patient_selection')
616 while 1:
617 pID = raw_input('a patient: ')
618 if pID == '':
619 break
620 try:
621 print pID
622 myPatient = gmPerson.cIdentity (aPK_obj = pID)
623 except:
624 _log.exception('Unable to set up patient with ID [%s]' % pID)
625 print "patient", pID, "can not be set up"
626 continue
627 print "ID ", myPatient.ID
628 print "name ", myPatient['description']
629 print "name ", myPatient['description_gender']
630 print "title ", myPatient['title']
631 print "dob ", myPatient['dob']
632 print "med age ", myPatient['medical_age']
633 for adr in myPatient.get_addresses():
634 print "address ", adr
635 print "--------------------------------------"
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050