1 """GNUmed measurement widgets."""
2
3 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
4 __license__ = "GPL"
5
6
7 import sys
8 import logging
9 import datetime as pyDT
10 import decimal
11 import os
12 import subprocess
13 import io
14 import os.path
15
16
17 import wx
18 import wx.grid
19 import wx.adv as wxh
20
21
22 if __name__ == '__main__':
23 sys.path.insert(0, '../../')
24 from Gnumed.pycommon import gmTools
25 from Gnumed.pycommon import gmNetworkTools
26 from Gnumed.pycommon import gmI18N
27 from Gnumed.pycommon import gmShellAPI
28 from Gnumed.pycommon import gmCfg
29 from Gnumed.pycommon import gmDateTime
30 from Gnumed.pycommon import gmMatchProvider
31 from Gnumed.pycommon import gmDispatcher
32 from Gnumed.pycommon import gmMimeLib
33
34 from Gnumed.business import gmPerson
35 from Gnumed.business import gmStaff
36 from Gnumed.business import gmPathLab
37 from Gnumed.business import gmPraxis
38 from Gnumed.business import gmLOINC
39 from Gnumed.business import gmForms
40 from Gnumed.business import gmPersonSearch
41 from Gnumed.business import gmOrganization
42 from Gnumed.business import gmHL7
43 from Gnumed.business import gmIncomingData
44 from Gnumed.business import gmDocuments
45
46 from Gnumed.wxpython import gmRegetMixin
47 from Gnumed.wxpython import gmPlugin
48 from Gnumed.wxpython import gmEditArea
49 from Gnumed.wxpython import gmPhraseWheel
50 from Gnumed.wxpython import gmListWidgets
51 from Gnumed.wxpython import gmGuiHelpers
52 from Gnumed.wxpython import gmAuthWidgets
53 from Gnumed.wxpython import gmOrganizationWidgets
54 from Gnumed.wxpython import gmEMRStructWidgets
55 from Gnumed.wxpython import gmCfgWidgets
56 from Gnumed.wxpython import gmDocumentWidgets
57
58
59 _log = logging.getLogger('gm.ui')
60
61
62
63
65
66 if parent is None:
67 parent = wx.GetApp().GetTopWindow()
68
69
70 paths = gmTools.gmPaths()
71 dlg = wx.FileDialog (
72 parent = parent,
73 message = _('Show HL7 file:'),
74
75 defaultDir = os.path.join(paths.home_dir, 'gnumed'),
76 wildcard = "hl7 files|*.hl7|HL7 files|*.HL7|all files|*",
77 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
78 )
79 choice = dlg.ShowModal()
80 hl7_name = dlg.GetPath()
81 dlg.DestroyLater()
82 if choice != wx.ID_OK:
83 return False
84
85 formatted_name = gmHL7.format_hl7_file (
86 hl7_name,
87 skip_empty_fields = True,
88 return_filename = True,
89 fix_hl7 = True
90 )
91 gmMimeLib.call_viewer_on_file(aFile = formatted_name, block = False)
92 return True
93
94
96
97 if parent is None:
98 parent = wx.GetApp().GetTopWindow()
99
100
101 paths = gmTools.gmPaths()
102 dlg = wx.FileDialog (
103 parent = parent,
104 message = _('Extract HL7 from XML file:'),
105
106 defaultDir = os.path.join(paths.home_dir, 'gnumed'),
107 wildcard = "xml files|*.xml|XML files|*.XML|all files|*",
108 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
109 )
110 choice = dlg.ShowModal()
111 xml_name = dlg.GetPath()
112 dlg.DestroyLater()
113 if choice != wx.ID_OK:
114 return False
115
116 target_dir = os.path.split(xml_name)[0]
117 xml_path = './/Message'
118 hl7_name = gmHL7.extract_HL7_from_XML_CDATA(xml_name, xml_path, target_dir = target_dir)
119 if hl7_name is None:
120 gmGuiHelpers.gm_show_error (
121 title = _('Extracting HL7 from XML file'),
122 error = (
123 'Cannot unwrap HL7 data from XML file\n'
124 '\n'
125 ' [%s]\n'
126 '\n'
127 '(CDATA of [%s] nodes)'
128 ) % (
129 xml_name,
130 xml_path
131 )
132 )
133 return False
134
135 gmDispatcher.send(signal = 'statustext', msg = _('Unwrapped HL7 into [%s] from [%s].') % (hl7_name, xml_name), beep = False)
136 return True
137
138
140
141 if parent is None:
142 parent = wx.GetApp().GetTopWindow()
143
144 paths = gmTools.gmPaths()
145 dlg = wx.FileDialog (
146 parent = parent,
147 message = _('Select HL7 file for staging:'),
148
149 defaultDir = os.path.join(paths.home_dir, 'gnumed'),
150 wildcard = ".hl7 files|*.hl7|.HL7 files|*.HL7|all files|*",
151 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
152 )
153 choice = dlg.ShowModal()
154 hl7_name = dlg.GetPath()
155 dlg.DestroyLater()
156 if choice != wx.ID_OK:
157 return False
158
159 target_dir = os.path.join(paths.home_dir, '.gnumed', 'hl7')
160 success, PID_names = gmHL7.split_hl7_file(hl7_name, target_dir = target_dir, encoding = 'utf8')
161 if not success:
162 gmGuiHelpers.gm_show_error (
163 title = _('Staging HL7 file'),
164 error = _(
165 'There was a problem with splitting the HL7 file\n'
166 '\n'
167 ' %s'
168 ) % hl7_name
169 )
170 return False
171
172 failed_files = []
173 for PID_name in PID_names:
174 if not gmHL7.stage_single_PID_hl7_file(PID_name, source = _('generic'), encoding = 'utf8'):
175 failed_files.append(PID_name)
176 if len(failed_files) > 0:
177 gmGuiHelpers.gm_show_error (
178 title = _('Staging HL7 file'),
179 error = _(
180 'There was a problem with staging the following files\n'
181 '\n'
182 ' %s'
183 ) % '\n '.join(failed_files)
184 )
185 return False
186
187 gmDispatcher.send(signal = 'statustext', msg = _('Staged HL7 from [%s].') % hl7_name, beep = False)
188 return True
189
190
212
213 def import_hl7(staged_item):
214 if staged_item is None:
215 return False
216 if 'HL7' not in staged_item['data_type']:
217 return False
218 unset_identity_on_error = False
219 if staged_item['pk_identity_disambiguated'] is None:
220 pat = gmPerson.gmCurrentPatient()
221 if pat.connected:
222 answer = gmGuiHelpers.gm_show_question (
223 title = _('Importing HL7 data'),
224 question = _(
225 'There has not been a patient explicitely associated\n'
226 'with this chunk of HL7 data. However, the data file\n'
227 'contains the following patient identification information:\n'
228 '\n'
229 ' %s\n'
230 '\n'
231 'Do you want to import the HL7 under the current patient ?\n'
232 '\n'
233 ' %s\n'
234 '\n'
235 'Selecting [NO] makes GNUmed try to find a patient matching the HL7 data.\n'
236 ) % (
237 staged_item.patient_identification,
238 pat['description_gender']
239 ),
240 cancel_button = True
241 )
242 if answer is None:
243 return False
244 if answer is True:
245 unset_identity_on_error = True
246 staged_item['pk_identity_disambiguated'] = pat.ID
247
248 success, log_name = gmHL7.process_staged_single_PID_hl7_file(staged_item)
249 if success:
250 return True
251
252 if unset_identity_on_error:
253 staged_item['pk_identity_disambiguated'] = None
254 staged_item.save()
255
256 gmGuiHelpers.gm_show_error (
257 error = _('Error processing HL7 data.'),
258 title = _('Processing staged HL7 data.')
259 )
260 return False
261
262
263 def delete(staged_item):
264 if staged_item is None:
265 return False
266 do_delete = gmGuiHelpers.gm_show_question (
267 title = _('Deleting incoming data'),
268 question = _(
269 'Do you really want to delete the incoming data ?\n'
270 '\n'
271 'Note that deletion is not reversible.'
272 )
273 )
274 if not do_delete:
275 return False
276 return gmIncomingData.delete_incoming_data(pk_incoming_data = staged_item['pk_incoming_data_unmatched'])
277
278 def refresh(lctrl):
279 incoming = gmIncomingData.get_incoming_data()
280 items = [ [
281 gmTools.coalesce(i['data_type'], ''),
282 '%s, %s (%s) %s' % (
283 gmTools.coalesce(i['lastnames'], ''),
284 gmTools.coalesce(i['firstnames'], ''),
285 gmDateTime.pydt_strftime(dt = i['dob'], format = '%Y %b %d', accuracy = gmDateTime.acc_days, none_str = _('unknown DOB')),
286 gmTools.coalesce(i['gender'], '')
287 ),
288 gmTools.coalesce(i['external_data_id'], ''),
289 i['pk_incoming_data_unmatched']
290 ] for i in incoming ]
291 lctrl.set_string_items(items)
292 lctrl.set_data(incoming)
293
294 gmListWidgets.get_choices_from_list (
295 parent = parent,
296 msg = None,
297 caption = _('Showing unmatched incoming data'),
298 columns = [ _('Type'), _('Identification'), _('Reference'), '#' ],
299 single_selection = True,
300 can_return_empty = False,
301 ignore_OK_button = True,
302 refresh_callback = refresh,
303
304
305 delete_callback = delete,
306 left_extra_button = [_('Show'), _('Show formatted HL7'), show_hl7],
307 middle_extra_button = [_('Import'), _('Import HL7 data into patient chart'), import_hl7]
308
309 )
310
311
312
313
343
344
345 -def edit_measurement(parent=None, measurement=None, single_entry=False, presets=None):
358
359
371
372 def delete(measurement):
373 gmPathLab.delete_test_result(result = measurement)
374 return True
375
376 def do_review(lctrl):
377 data = lctrl.get_selected_item_data()
378 if len(data) == 0:
379 return
380 return review_tests(parent = parent, tests = data)
381
382 def do_plot(lctrl):
383 data = lctrl.get_selected_item_data()
384 if len(data) == 0:
385 return
386 return plot_measurements(parent = parent, tests = data)
387
388 def get_tooltip(measurement):
389 return measurement.format(with_review=True, with_evaluation=True, with_ranges=True)
390
391 def refresh(lctrl):
392 results = emr.get_test_results(order_by = 'clin_when DESC, unified_abbrev, unified_name')
393 items = [ [
394 gmDateTime.pydt_strftime (
395 r['clin_when'],
396 '%Y %b %d %H:%M',
397 accuracy = gmDateTime.acc_minutes
398 ),
399 r['unified_abbrev'],
400 '%s%s%s%s' % (
401 gmTools.bool2subst (
402 boolean = (not r['reviewed'] or (not r['review_by_you'] and r['you_are_responsible'])),
403 true_return = 'u' + gmTools.u_writing_hand,
404 false_return = ''
405 ),
406 r['unified_val'],
407 gmTools.coalesce(r['val_unit'], '', ' %s'),
408 gmTools.coalesce(r['abnormality_indicator'], '', ' %s')
409 ),
410 r['unified_name'],
411
412
413
414
415
416
417
418
419
420
421
422
423 gmTools.coalesce(r['comment'], ''),
424 r['pk_test_result']
425 ] for r in results ]
426 lctrl.set_string_items(items)
427 lctrl.set_data(results)
428
429
430 msg = _('Test results (ordered reverse-chronologically)')
431
432 return gmListWidgets.get_choices_from_list (
433 parent = parent,
434 msg = msg,
435 caption = _('Showing test results.'),
436 columns = [ _('When'), _('Abbrev'), _('Value'), _('Name'), _('Comment'), '#' ],
437 single_selection = single_selection,
438 can_return_empty = False,
439 refresh_callback = refresh,
440 edit_callback = edit,
441 new_callback = edit,
442 delete_callback = delete,
443 list_tooltip_callback = get_tooltip,
444 left_extra_button = (_('Review'), _('Review current selection'), do_review, True),
445 middle_extra_button = (_('Plot'), _('Plot current selection'), do_plot, True)
446 )
447
448
466
467
498
499
501
502 option = 'form_templates.default_gnuplot_template'
503
504 dbcfg = gmCfg.cCfgSQL()
505
506
507 default_template_name = dbcfg.get2 (
508 option = option,
509 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
510 bias = 'user'
511 )
512
513
514 if default_template_name is None:
515 gmDispatcher.send('statustext', msg = _('No default Gnuplot template configured.'), beep = False)
516 default_template = configure_default_gnuplot_template(parent = parent)
517
518 if default_template is None:
519 gmGuiHelpers.gm_show_error (
520 aMessage = _('There is no default Gnuplot one-type script template configured.'),
521 aTitle = _('Plotting test results')
522 )
523 return None
524 return default_template
525
526
527
528 try:
529 name, ver = default_template_name.split(' - ')
530 except:
531
532 _log.exception('problem splitting Gnuplot script template name [%s]', default_template_name)
533 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading Gnuplot script template.'), beep = True)
534 return None
535
536 default_template = gmForms.get_form_template(name_long = name, external_version = ver)
537 if default_template is None:
538 default_template = configure_default_gnuplot_template(parent = parent)
539
540 if default_template is None:
541 gmGuiHelpers.gm_show_error (
542 aMessage = _('Cannot load default Gnuplot script template [%s - %s]') % (name, ver),
543 aTitle = _('Plotting test results')
544 )
545 return None
546
547 return default_template
548
549
550 -def plot_measurements(parent=None, tests=None, format=None, show_year = True, use_default_template=False):
576
577
578 -def plot_adjacent_measurements(parent=None, test=None, format=None, show_year=True, plot_singular_result=True, use_default_template=False):
579
580 earlier, later = test.get_adjacent_results(desired_earlier_results = 2, desired_later_results = 2)
581 results2plot = []
582 if earlier is not None:
583 results2plot.extend(earlier)
584 results2plot.append(test)
585 if later is not None:
586 results2plot.extend(later)
587 if len(results2plot) == 1:
588 if not plot_singular_result:
589 return
590 plot_measurements (
591 parent = parent,
592 tests = results2plot,
593 format = format,
594 show_year = show_year,
595 use_default_template = use_default_template
596 )
597
598
599
600
601
602
603
604
605
606
607
608
609 from Gnumed.wxGladeWidgets import wxgLabRelatedDocumentsPnl
610
754
755
756 from Gnumed.wxGladeWidgets import wxgMeasurementsAsListPnl
757
758 -class cMeasurementsAsListPnl(wxgMeasurementsAsListPnl.wxgMeasurementsAsListPnl, gmRegetMixin.cRegetOnPaintMixin):
759 """A class for displaying all measurement results as a simple list.
760
761 - operates on a cPatient instance handed to it and NOT on the currently active patient
762 """
772
773
774
775
777 self._LCTRL_results.set_columns([_('When'), _('Test'), _('Result'), _('Reference')])
778 self._LCTRL_results.edit_callback = self._on_edit
779 self._PNL_related_documents.lab_reference = None
780
781
784
785
787 if self.__patient is None:
788 self._LCTRL_results.set_string_items([])
789 self._TCTRL_measurements.SetValue('')
790 self._PNL_related_documents.lab_reference = None
791 return
792
793 results = self.__patient.emr.get_test_results(order_by = 'clin_when DESC, unified_abbrev, unified_name')
794 items = []
795 data = []
796 for r in results:
797 range_info = gmTools.coalesce (
798 r.formatted_clinical_range,
799 r.formatted_normal_range
800 )
801 review = gmTools.bool2subst (
802 r['reviewed'],
803 '',
804 ' ' + gmTools.u_writing_hand,
805 ' ' + gmTools.u_writing_hand
806 )
807 items.append ([
808 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes),
809 r['abbrev_tt'],
810 '%s%s%s%s' % (
811 gmTools.strip_empty_lines(text = r['unified_val'])[0],
812 gmTools.coalesce(r['val_unit'], '', ' %s'),
813 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
814 review
815 ),
816 gmTools.coalesce(range_info, '')
817 ])
818 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
819
820 self._LCTRL_results.set_string_items(items)
821 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
822 self._LCTRL_results.set_data(data)
823 if len(items) > 0:
824 self._LCTRL_results.Select(idx = 0, on = 1)
825 self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted'])
826
827 self._LCTRL_results.SetFocus()
828
829
836
837
838
839
841 if self.__patient is None:
842 return True
843
844 if kwds['pk_identity'] is not None:
845 if kwds['pk_identity'] != self.__patient.ID:
846 return True
847
848 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
849 return True
850
851 self._schedule_data_reget()
852 return True
853
854
860
861
862
863
865 self.__repopulate_ui()
866 return True
867
868
869
870
872 return self.__patient
873
875 if (self.__patient is None) and (patient is None):
876 return
877 if (self.__patient is None) or (patient is None):
878 self.__patient = patient
879 self._schedule_data_reget()
880 return
881 if self.__patient.ID == patient.ID:
882 return
883 self.__patient = patient
884 self._schedule_data_reget()
885
886 patient = property(_get_patient, _set_patient)
887
888
889 from Gnumed.wxGladeWidgets import wxgMeasurementsByDayPnl
890
891 -class cMeasurementsByDayPnl(wxgMeasurementsByDayPnl.wxgMeasurementsByDayPnl, gmRegetMixin.cRegetOnPaintMixin):
892 """A class for displaying measurement results as a list partitioned by day.
893
894 - operates on a cPatient instance handed to it and NOT on the currently active patient
895 """
906
907
908
909
911 self._LCTRL_days.set_columns([_('Day')])
912 self._LCTRL_results.set_columns([_('Time'), _('Test'), _('Result'), _('Reference')])
913 self._LCTRL_results.edit_callback = self._on_edit
914 self._PNL_related_documents.lab_reference = None
915
916
919
920
926
927
946
947
954
955
956
957
959 if self.__patient is None:
960 return True
961
962 if kwds['pk_identity'] is not None:
963 if kwds['pk_identity'] != self.__patient.ID:
964 return True
965
966 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
967 return True
968
969 self._schedule_data_reget()
970 return True
971
972
974 event.Skip()
975
976 day = self._LCTRL_days.get_item_data(item_idx = event.Index)['clin_when_day']
977 results = self.__patient.emr.get_results_for_day(timestamp = day)
978 items = []
979 data = []
980 for r in results:
981 range_info = gmTools.coalesce (
982 r.formatted_clinical_range,
983 r.formatted_normal_range
984 )
985 review = gmTools.bool2subst (
986 r['reviewed'],
987 '',
988 ' ' + gmTools.u_writing_hand,
989 ' ' + gmTools.u_writing_hand
990 )
991 items.append ([
992 gmDateTime.pydt_strftime(r['clin_when'], '%H:%M'),
993 r['abbrev_tt'],
994 '%s%s%s%s' % (
995 gmTools.strip_empty_lines(text = r['unified_val'])[0],
996 gmTools.coalesce(r['val_unit'], '', ' %s'),
997 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
998 review
999 ),
1000 gmTools.coalesce(range_info, '')
1001 ])
1002 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
1003
1004 self._LCTRL_results.set_string_items(items)
1005 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1006 self._LCTRL_results.set_data(data)
1007 self._LCTRL_results.Select(idx = 0, on = 1)
1008
1009
1015
1016
1017
1018
1020 self.__repopulate_ui()
1021 return True
1022
1023
1024
1025
1027 return self.__patient
1028
1030 if (self.__patient is None) and (patient is None):
1031 return
1032 if patient is None:
1033 self.__patient = None
1034 self.__clear()
1035 return
1036 if self.__patient is None:
1037 self.__patient = patient
1038 self._schedule_data_reget()
1039 return
1040 if self.__patient.ID == patient.ID:
1041 return
1042 self.__patient = patient
1043 self._schedule_data_reget()
1044
1045 patient = property(_get_patient, _set_patient)
1046
1047
1048 from Gnumed.wxGladeWidgets import wxgMeasurementsByIssuePnl
1049
1050 -class cMeasurementsByIssuePnl(wxgMeasurementsByIssuePnl.wxgMeasurementsByIssuePnl, gmRegetMixin.cRegetOnPaintMixin):
1051 """A class for displaying measurement results as a list partitioned by issue/episode.
1052
1053 - operates on a cPatient instance handed to it and NOT on the currently active patient
1054 """
1064
1065
1066
1067
1069 self._LCTRL_issues.set_columns([_('Problem')])
1070 self._LCTRL_results.set_columns([_('When'), _('Test'), _('Result'), _('Reference')])
1071 self._PNL_related_documents.lab_reference = None
1072
1073
1079
1080
1086
1087
1103
1104
1111
1112
1113
1114
1116 if self.__patient is None:
1117 return True
1118
1119 if kwds['pk_identity'] is not None:
1120 if kwds['pk_identity'] != self.__patient.ID:
1121 return True
1122
1123 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1124 return True
1125
1126 self._schedule_data_reget()
1127 return True
1128
1129
1131 event.Skip()
1132
1133 pk_issue = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_issue']
1134 if pk_issue is None:
1135 pk_episode = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_episode']
1136 results = self.__patient.emr.get_results_for_episode(pk_episode = pk_episode)
1137 else:
1138 results = self.__patient.emr.get_results_for_issue(pk_health_issue = pk_issue)
1139 items = []
1140 data = []
1141 for r in results:
1142 range_info = gmTools.coalesce (
1143 r.formatted_clinical_range,
1144 r.formatted_normal_range
1145 )
1146 review = gmTools.bool2subst (
1147 r['reviewed'],
1148 '',
1149 ' ' + gmTools.u_writing_hand,
1150 ' ' + gmTools.u_writing_hand
1151 )
1152 items.append ([
1153 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M'),
1154 r['abbrev_tt'],
1155 '%s%s%s%s' % (
1156 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1157 gmTools.coalesce(r['val_unit'], '', ' %s'),
1158 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1159 review
1160 ),
1161 gmTools.coalesce(range_info, '')
1162 ])
1163 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
1164
1165 self._LCTRL_results.set_string_items(items)
1166 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1167 self._LCTRL_results.set_data(data)
1168 self._LCTRL_results.Select(idx = 0, on = 1)
1169 self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted'])
1170
1171
1177
1178
1179
1180
1182 self.__repopulate_ui()
1183 return True
1184
1185
1186
1187
1189 return self.__patient
1190
1192 if (self.__patient is None) and (patient is None):
1193 return
1194 if patient is None:
1195 self.__patient = None
1196 self.__clear()
1197 return
1198 if self.__patient is None:
1199 self.__patient = patient
1200 self._schedule_data_reget()
1201 return
1202 if self.__patient.ID == patient.ID:
1203 return
1204 self.__patient = patient
1205 self._schedule_data_reget()
1206
1207 patient = property(_get_patient, _set_patient)
1208
1209
1210 from Gnumed.wxGladeWidgets import wxgMeasurementsByBatteryPnl
1211
1213 """A grid class for displaying measurement results filtered by battery/panel.
1214
1215 - operates on a cPatient instance handed to it and NOT on the currently active patient
1216 """
1226
1227
1228
1229
1232
1233
1239
1240
1242 self._GRID_results_battery.patient = self.__patient
1243 return True
1244
1245
1247 if panel is None:
1248 self._TCTRL_panel_comment.SetValue('')
1249 self._GRID_results_battery.panel_to_show = None
1250 else:
1251 pnl = self._PRW_panel.GetData(as_instance = True)
1252 self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
1253 pnl['comment'],
1254 ''
1255 ))
1256 self._GRID_results_battery.panel_to_show = pnl
1257
1258
1259
1261 self._TCTRL_panel_comment.SetValue('')
1262 if self._PRW_panel.GetValue().strip() == '':
1263 self._GRID_results_battery.panel_to_show = None
1264
1265
1266
1267
1268
1270 if self.__patient is None:
1271 return True
1272
1273 if kwds['pk_identity'] is not None:
1274 if kwds['pk_identity'] != self.__patient.ID:
1275 return True
1276
1277 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1278 return True
1279
1280 self._schedule_data_reget()
1281 return True
1282
1283
1286
1287
1289 wx.CallAfter(self.__on_panel_selected, panel=panel)
1290
1291
1293 wx.CallAfter(self.__on_panel_selection_modified)
1294
1295
1296
1297
1299 self.__repopulate_ui()
1300 return True
1301
1302
1303
1304
1306 return self.__patient
1307
1309 if (self.__patient is None) and (patient is None):
1310 return
1311 if (self.__patient is None) or (patient is None):
1312 self.__patient = patient
1313 self._schedule_data_reget()
1314 return
1315 if self.__patient.ID == patient.ID:
1316 return
1317 self.__patient = patient
1318 self._schedule_data_reget()
1319
1320 patient = property(_get_patient, _set_patient)
1321
1322
1323 from Gnumed.wxGladeWidgets import wxgMeasurementsAsMostRecentListPnl
1324
1326 """A list ctrl class for displaying measurement results.
1327
1328 - most recent results
1329 - possibly filtered by battery/panel
1330
1331 - operates on a cPatient instance handed to it and NOT on the currently active patient
1332 """
1342
1343
1344
1345
1347 self._LCTRL_results.set_columns([_('Test'), _('Result'), _('When'), _('Range')])
1348 self._CHBOX_show_missing.Disable()
1349 self._PNL_related_documents.lab_reference = None
1350
1351
1360
1361
1363
1364 self._TCTRL_details.SetValue('')
1365 self._PNL_related_documents.lab_reference = None
1366
1367 pnl = self._PRW_panel.GetData(as_instance = True)
1368 if pnl is None:
1369 results = gmPathLab.get_most_recent_result_for_test_types (
1370 pk_patient = self.__patient.ID,
1371 consider_meta_type = True
1372 )
1373 else:
1374 results = pnl.get_most_recent_results (
1375 pk_patient = self.__patient.ID,
1376
1377 group_by_meta_type = True,
1378 include_missing = self._CHBOX_show_missing.IsChecked()
1379 )
1380 items = []
1381 data = []
1382 for r in results:
1383 if isinstance(r, gmPathLab.cTestResult):
1384 result_type = gmTools.coalesce (
1385 value2test = r['pk_meta_test_type'],
1386 return_instead = r['abbrev_tt'],
1387 value2return = '%s%s' % (gmTools.u_sum, r['abbrev_meta'])
1388 )
1389 review = gmTools.bool2subst (
1390 r['reviewed'],
1391 '',
1392 ' ' + gmTools.u_writing_hand,
1393 ' ' + gmTools.u_writing_hand
1394 )
1395 result_val = '%s%s%s%s' % (
1396 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1397 gmTools.coalesce(r['val_unit'], '', ' %s'),
1398 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1399 review
1400 )
1401 result_when = _('%s ago (%s)') % (
1402 gmDateTime.format_interval_medically(interval = gmDateTime.pydt_now_here() - r['clin_when']),
1403 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes)
1404 )
1405 range_info = gmTools.coalesce (
1406 r.formatted_clinical_range,
1407 r.formatted_normal_range
1408 )
1409 tt = r.format(with_source_data = True)
1410 else:
1411 result_type = r
1412 result_val = _('missing')
1413 loinc_data = gmLOINC.loinc2data(r)
1414 if loinc_data is None:
1415 result_when = _('LOINC not found')
1416 tt = u''
1417 else:
1418 result_when = loinc_data['term']
1419 tt = gmLOINC.format_loinc(r)
1420 range_info = None
1421 items.append([result_type, result_val, result_when, gmTools.coalesce(range_info, '')])
1422 data.append({'data': r, 'formatted': tt})
1423
1424 self._LCTRL_results.set_string_items(items)
1425 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1426 self._LCTRL_results.set_data(data)
1427
1428 if len(items) > 0:
1429 self._LCTRL_results.Select(idx = 0, on = 1)
1430 self._LCTRL_results.SetFocus()
1431
1432 return True
1433
1434
1436 if panel is None:
1437 self._TCTRL_panel_comment.SetValue('')
1438 self._CHBOX_show_missing.Disable()
1439 else:
1440 pnl = self._PRW_panel.GetData(as_instance = True)
1441 self._TCTRL_panel_comment.SetValue(gmTools.coalesce(pnl['comment'], ''))
1442 self.__repopulate_ui()
1443 self._CHBOX_show_missing.Enable()
1444
1445
1447 self._TCTRL_panel_comment.SetValue('')
1448 if self._PRW_panel.Value.strip() == u'':
1449 self.__repopulate_ui()
1450 self._CHBOX_show_missing.Disable()
1451
1452
1453
1454
1456 if self.__patient is None:
1457 return True
1458
1459 if kwds['pk_identity'] is not None:
1460 if kwds['pk_identity'] != self.__patient.ID:
1461 return True
1462
1463 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results', 'clin.test_panel']:
1464 return True
1465
1466 self._schedule_data_reget()
1467 return True
1468
1469
1472
1473
1475 wx.CallAfter(self.__on_panel_selected, panel = panel)
1476
1477
1479 wx.CallAfter(self.__on_panel_selection_modified)
1480
1481
1490
1491
1499
1500
1502 event.Skip()
1503
1504 if self._PRW_panel.GetData(as_instance = False) is None:
1505 return
1506 self.__repopulate_ui()
1507
1508
1509
1510
1512 self.__repopulate_ui()
1513 return True
1514
1515
1516
1517
1519 return self.__patient
1520
1522 if (self.__patient is None) and (patient is None):
1523 return
1524 if (self.__patient is None) or (patient is None):
1525 self.__patient = patient
1526 self._schedule_data_reget()
1527 return
1528 if self.__patient.ID == patient.ID:
1529 return
1530 self.__patient = patient
1531 self._schedule_data_reget()
1532
1533 patient = property(_get_patient, _set_patient)
1534
1535
1536 from Gnumed.wxGladeWidgets import wxgMeasurementsAsTablePnl
1537
1538 -class cMeasurementsAsTablePnl(wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl, gmRegetMixin.cRegetOnPaintMixin):
1539 """A panel for holding a grid displaying all measurement results.
1540
1541 - operates on a cPatient instance handed to it and NOT on the currently active patient
1542 """
1552
1553
1554
1555
1557 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
1558
1559 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
1560 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
1561
1562 item = self.__action_button_popup.Append(-1, _('Plot'))
1563 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573 item = self.__action_button_popup.Append(-1, _('&Delete'))
1574 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
1575
1576
1577
1578
1579 self._GRID_results_all.show_by_panel = False
1580
1581
1584
1585
1587 self._GRID_results_all.patient = self.__patient
1588
1589 self.Layout()
1590 return True
1591
1592
1595
1596
1599
1600
1603
1604
1605
1606
1608 if self.__patient is None:
1609 return True
1610
1611 if kwds['pk_identity'] is not None:
1612 if kwds['pk_identity'] != self.__patient.ID:
1613 return True
1614
1615 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1616 return True
1617
1618 self._schedule_data_reget()
1619 return True
1620
1621
1624
1625
1629
1630
1633
1634
1640
1641
1642
1643
1645 self.__repopulate_ui()
1646 return True
1647
1648
1649
1650
1652 return self.__patient
1653
1655 if (self.__patient is None) and (patient is None):
1656 return
1657 if (self.__patient is None) or (patient is None):
1658 self.__patient = patient
1659 self._schedule_data_reget()
1660 return
1661 if self.__patient.ID == patient.ID:
1662 return
1663 self.__patient = patient
1664 self._schedule_data_reget()
1665
1666 patient = property(_get_patient, _set_patient)
1667
1668
1669
1670
1672 """Notebook displaying measurements pages:
1673
1674 - by test battery
1675 - by day
1676 - by issue/episode
1677 - most-recent list, perhaps by panel
1678 - full grid
1679 - full list
1680
1681 Used as a main notebook plugin page.
1682
1683 Operates on the active patient.
1684 """
1685
1700
1701
1702
1703
1705 for page_idx in range(self.GetPageCount()):
1706 page = self.GetPage(page_idx)
1707 page.patient = None
1708
1709
1710 - def _post_patient_selection(self, **kwds):
1711 for page_idx in range(self.GetPageCount()):
1712 page = self.GetPage(page_idx)
1713 page.patient = self.__patient.patient
1714
1715
1716
1717
1719 if self.__patient.connected:
1720 pat = self.__patient.patient
1721 else:
1722 pat = None
1723 for page_idx in range(self.GetPageCount()):
1724 page = self.GetPage(page_idx)
1725 page.patient = pat
1726
1727 return True
1728
1729
1730
1731
1733
1734
1735 new_page = cMeasurementsByDayPnl(self, -1)
1736 new_page.patient = None
1737 self.AddPage (
1738 page = new_page,
1739 text = _('Days'),
1740 select = True
1741 )
1742
1743
1744 new_page = cMeasurementsByIssuePnl(self, -1)
1745 new_page.patient = None
1746 self.AddPage (
1747 page = new_page,
1748 text = _('Problems'),
1749 select = False
1750 )
1751
1752
1753 new_page = cMeasurementsByBatteryPnl(self, -1)
1754 new_page.patient = None
1755 self.AddPage (
1756 page = new_page,
1757 text = _('Panels'),
1758 select = False
1759 )
1760
1761
1762 new_page = cMeasurementsAsMostRecentListPnl(self, -1)
1763 new_page.patient = None
1764 self.AddPage (
1765 page = new_page,
1766 text = _('Most recent'),
1767 select = False
1768 )
1769
1770
1771 new_page = cMeasurementsAsTablePnl(self, -1)
1772 new_page.patient = None
1773 self.AddPage (
1774 page = new_page,
1775 text = _('Table'),
1776 select = False
1777 )
1778
1779
1780 new_page = cMeasurementsAsListPnl(self, -1)
1781 new_page.patient = None
1782 self.AddPage (
1783 page = new_page,
1784 text = _('List'),
1785 select = False
1786 )
1787
1788
1789
1790
1792 return self.__patient
1793
1795 self.__patient = patient
1796 if self.__patient.connected:
1797 pat = self.__patient.patient
1798 else:
1799 pat = None
1800 for page_idx in range(self.GetPageCount()):
1801 page = self.GetPage(page_idx)
1802 page.patient = pat
1803
1804 patient = property(_get_patient, _set_patient)
1805
1806
1808 """A grid class for displaying measurement results.
1809
1810 - operates on a cPatient instance handed to it
1811 - does NOT listen to the currently active patient
1812 - thereby it can display any patient at any time
1813 """
1814
1815
1816
1817
1818
1820
1821 wx.grid.Grid.__init__(self, *args, **kwargs)
1822
1823 self.__patient = None
1824 self.__panel_to_show = None
1825 self.__show_by_panel = False
1826 self.__cell_data = {}
1827 self.__row_label_data = []
1828 self.__col_label_data = []
1829
1830 self.__prev_row = None
1831 self.__prev_col = None
1832 self.__prev_label_row = None
1833 self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::'))
1834
1835 self.__init_ui()
1836 self.__register_events()
1837
1838
1839
1840
1842 if not self.IsSelection():
1843 gmDispatcher.send(signal = 'statustext', msg = _('No results selected for deletion.'))
1844 return True
1845
1846 selected_cells = self.get_selected_cells()
1847 if len(selected_cells) > 20:
1848 results = None
1849 msg = _(
1850 'There are %s results marked for deletion.\n'
1851 '\n'
1852 'Are you sure you want to delete these results ?'
1853 ) % len(selected_cells)
1854 else:
1855 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1856 txt = '\n'.join([ '%s %s (%s): %s %s%s' % (
1857 r['clin_when'].strftime('%x %H:%M'),
1858 r['unified_abbrev'],
1859 r['unified_name'],
1860 r['unified_val'],
1861 r['val_unit'],
1862 gmTools.coalesce(r['abnormality_indicator'], '', ' (%s)')
1863 ) for r in results
1864 ])
1865 msg = _(
1866 'The following results are marked for deletion:\n'
1867 '\n'
1868 '%s\n'
1869 '\n'
1870 'Are you sure you want to delete these results ?'
1871 ) % txt
1872
1873 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
1874 self,
1875 -1,
1876 caption = _('Deleting test results'),
1877 question = msg,
1878 button_defs = [
1879 {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False},
1880 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True}
1881 ]
1882 )
1883 decision = dlg.ShowModal()
1884
1885 if decision == wx.ID_YES:
1886 if results is None:
1887 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1888 for result in results:
1889 gmPathLab.delete_test_result(result)
1890
1891
1893 if not self.IsSelection():
1894 gmDispatcher.send(signal = 'statustext', msg = _('Cannot sign results. No results selected.'))
1895 return True
1896
1897 selected_cells = self.get_selected_cells()
1898 tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1899
1900 return review_tests(parent = self, tests = tests)
1901
1902
1904
1905 if not self.IsSelection():
1906 gmDispatcher.send(signal = 'statustext', msg = _('Cannot plot results. No results selected.'))
1907 return True
1908
1909 tests = self.__cells_to_data (
1910 cells = self.get_selected_cells(),
1911 exclude_multi_cells = False,
1912 auto_include_multi_cells = True
1913 )
1914
1915 plot_measurements(parent = self, tests = tests)
1916
1918
1919 sel_block_top_left = self.GetSelectionBlockTopLeft()
1920 sel_block_bottom_right = self.GetSelectionBlockBottomRight()
1921 sel_cols = self.GetSelectedCols()
1922 sel_rows = self.GetSelectedRows()
1923
1924 selected_cells = []
1925
1926
1927 selected_cells += self.GetSelectedCells()
1928
1929
1930 selected_cells += list (
1931 (row, col)
1932 for row in sel_rows
1933 for col in range(self.GetNumberCols())
1934 )
1935
1936
1937 selected_cells += list (
1938 (row, col)
1939 for row in range(self.GetNumberRows())
1940 for col in sel_cols
1941 )
1942
1943
1944 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()):
1945 selected_cells += [
1946 (row, col)
1947 for row in range(top_left[0], bottom_right[0] + 1)
1948 for col in range(top_left[1], bottom_right[1] + 1)
1949 ]
1950
1951 return set(selected_cells)
1952
1953 - def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
1954 """Select a range of cells according to criteria.
1955
1956 unsigned_only: include only those which are not signed at all yet
1957 accountable_only: include only those for which the current user is responsible
1958 keep_preselections: broaden (rather than replace) the range of selected cells
1959
1960 Combinations are powerful !
1961 """
1962 wx.BeginBusyCursor()
1963 self.BeginBatch()
1964
1965 if not keep_preselections:
1966 self.ClearSelection()
1967
1968 for col_idx in self.__cell_data.keys():
1969 for row_idx in self.__cell_data[col_idx].keys():
1970
1971
1972 do_not_include = False
1973 for result in self.__cell_data[col_idx][row_idx]:
1974 if unsigned_only:
1975 if result['reviewed']:
1976 do_not_include = True
1977 break
1978 if accountables_only:
1979 if not result['you_are_responsible']:
1980 do_not_include = True
1981 break
1982 if do_not_include:
1983 continue
1984
1985 self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True)
1986
1987 self.EndBatch()
1988 wx.EndBusyCursor()
1989
1990
1992 self.empty_grid()
1993 if self.__patient is None:
1994 return
1995
1996 if self.__show_by_panel:
1997 if self.__panel_to_show is None:
1998 return
1999 tests = self.__panel_to_show.get_test_types_for_results (
2000 self.__patient.ID,
2001 order_by = 'unified_abbrev',
2002 unique_meta_types = True
2003 )
2004 self.__repopulate_grid (
2005 tests4rows = tests,
2006 test_pks2show = [ tt['pk_test_type'] for tt in self.__panel_to_show['test_types'] ]
2007 )
2008 return
2009
2010 emr = self.__patient.emr
2011 tests = emr.get_test_types_for_results(order_by = 'unified_abbrev', unique_meta_types = True)
2012 self.__repopulate_grid(tests4rows = tests)
2013
2014
2016
2017 if len(tests4rows) == 0:
2018 return
2019
2020 emr = self.__patient.emr
2021
2022 self.__row_label_data = tests4rows
2023 row_labels = [ '%s%s' % (
2024 gmTools.bool2subst(test_type['is_fake_meta_type'], '', gmTools.u_sum, ''),
2025 test_type['unified_abbrev']
2026 ) for test_type in self.__row_label_data
2027 ]
2028
2029 self.__col_label_data = [ d['clin_when_day'] for d in emr.get_dates_for_results (
2030 tests = test_pks2show,
2031 reverse_chronological = True
2032 )]
2033 col_labels = [ gmDateTime.pydt_strftime(date, self.__date_format, accuracy = gmDateTime.acc_days) for date in self.__col_label_data ]
2034
2035 results = emr.get_test_results_by_date (
2036 tests = test_pks2show,
2037 reverse_chronological = True
2038 )
2039
2040 self.BeginBatch()
2041
2042
2043 self.AppendRows(numRows = len(row_labels))
2044 for row_idx in range(len(row_labels)):
2045 self.SetRowLabelValue(row_idx, row_labels[row_idx])
2046
2047
2048 self.AppendCols(numCols = len(col_labels))
2049 for col_idx in range(len(col_labels)):
2050 self.SetColLabelValue(col_idx, col_labels[col_idx])
2051
2052
2053 for result in results:
2054 row_idx = row_labels.index('%s%s' % (
2055 gmTools.bool2subst(result['is_fake_meta_type'], '', gmTools.u_sum, ''),
2056 result['unified_abbrev']
2057 ))
2058 col_idx = col_labels.index(gmDateTime.pydt_strftime(result['clin_when'], self.__date_format, accuracy = gmDateTime.acc_days))
2059
2060 try:
2061 self.__cell_data[col_idx]
2062 except KeyError:
2063 self.__cell_data[col_idx] = {}
2064
2065
2066 if row_idx in self.__cell_data[col_idx]:
2067 self.__cell_data[col_idx][row_idx].append(result)
2068 self.__cell_data[col_idx][row_idx].sort(key = lambda x: x['clin_when'], reverse = True)
2069 else:
2070 self.__cell_data[col_idx][row_idx] = [result]
2071
2072
2073 vals2display = []
2074 cell_has_out_of_bounds_value = False
2075 for sub_result in self.__cell_data[col_idx][row_idx]:
2076
2077 if sub_result.is_considered_abnormal:
2078 cell_has_out_of_bounds_value = True
2079
2080 abnormality_indicator = sub_result.formatted_abnormality_indicator
2081 if abnormality_indicator is None:
2082 abnormality_indicator = ''
2083 if abnormality_indicator != '':
2084 abnormality_indicator = ' (%s)' % abnormality_indicator[:3]
2085
2086 missing_review = False
2087
2088
2089 if not sub_result['reviewed']:
2090 missing_review = True
2091
2092 else:
2093
2094 if sub_result['you_are_responsible'] and not sub_result['review_by_you']:
2095 missing_review = True
2096
2097 needs_superscript = False
2098
2099
2100 if sub_result.is_long_text:
2101 lines = gmTools.strip_empty_lines (
2102 text = sub_result['unified_val'],
2103 eol = '\n',
2104 return_list = True
2105 )
2106 needs_superscript = True
2107 tmp = lines[0][:7]
2108 else:
2109 val = gmTools.strip_empty_lines (
2110 text = sub_result['unified_val'],
2111 eol = '\n',
2112 return_list = False
2113 ).replace('\n', '//')
2114 if len(val) > 8:
2115 needs_superscript = True
2116 tmp = val[:7]
2117 else:
2118 tmp = '%.8s' % val[:8]
2119
2120
2121 tmp = '%s%.6s' % (tmp, abnormality_indicator)
2122
2123
2124 has_sub_result_comment = gmTools.coalesce (
2125 gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']),
2126 ''
2127 ).strip() != ''
2128 if has_sub_result_comment:
2129 needs_superscript = True
2130
2131 if needs_superscript:
2132 tmp = '%s%s' % (tmp, gmTools.u_superscript_one)
2133
2134
2135 if missing_review:
2136 tmp = '%s %s' % (tmp, gmTools.u_writing_hand)
2137 else:
2138 if sub_result['is_clinically_relevant']:
2139 tmp += ' !'
2140
2141
2142 if len(self.__cell_data[col_idx][row_idx]) > 1:
2143 tmp = '%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp)
2144
2145 vals2display.append(tmp)
2146
2147 self.SetCellValue(row_idx, col_idx, '\n'.join(vals2display))
2148 self.SetCellAlignment(row_idx, col_idx, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159 if cell_has_out_of_bounds_value:
2160
2161 self.SetCellBackgroundColour(row_idx, col_idx, 'PALE TURQUOISE')
2162
2163 self.EndBatch()
2164
2165 self.AutoSize()
2166 self.AdjustScrollbars()
2167 self.ForceRefresh()
2168
2169
2170
2171 return
2172
2173
2175 self.BeginBatch()
2176 self.ClearGrid()
2177
2178
2179 if self.GetNumberRows() > 0:
2180 self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
2181 if self.GetNumberCols() > 0:
2182 self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
2183 self.EndBatch()
2184 self.__cell_data = {}
2185 self.__row_label_data = []
2186 self.__col_label_data = []
2187
2188
2206
2207
2233
2234
2235
2236
2238
2239 self.SetMinSize((10, 10))
2240
2241 self.CreateGrid(0, 1)
2242 self.EnableEditing(0)
2243 self.EnableDragGridSize(1)
2244
2245
2246
2247
2248
2249
2250 self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE)
2251
2252 self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE)
2253 font = self.GetLabelFont()
2254 font.SetWeight(wx.FONTWEIGHT_LIGHT)
2255 self.SetLabelFont(font)
2256
2257
2258 dbcfg = gmCfg.cCfgSQL()
2259 url = dbcfg.get2 (
2260 option = 'external.urls.measurements_encyclopedia',
2261 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
2262 bias = 'user',
2263 default = gmPathLab.URL_test_result_information
2264 )
2265
2266 self.__WIN_corner = self.GetGridCornerLabelWindow()
2267
2268 LNK_lab = wxh.HyperlinkCtrl (
2269 self.__WIN_corner,
2270 -1,
2271 label = _('Tests'),
2272 style = wxh.HL_DEFAULT_STYLE
2273 )
2274 LNK_lab.SetURL(url)
2275 LNK_lab.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND))
2276 LNK_lab.SetToolTip(_(
2277 'Navigate to an encyclopedia of measurements\n'
2278 'and test methods on the web.\n'
2279 '\n'
2280 ' <%s>'
2281 ) % url)
2282
2283 SZR_inner = wx.BoxSizer(wx.HORIZONTAL)
2284 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)
2285 SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0)
2286 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)
2287
2288 SZR_corner = wx.BoxSizer(wx.VERTICAL)
2289 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)
2290 SZR_corner.Add(SZR_inner, 0, wx.EXPAND)
2291 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)
2292
2293 self.__WIN_corner.SetSizer(SZR_corner)
2294 SZR_corner.Fit(self.__WIN_corner)
2295
2296
2298 self.__WIN_corner.Layout()
2299
2300
2301 - def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
2302 """List of <cells> must be in row / col order."""
2303 data = []
2304 for row, col in cells:
2305 try:
2306
2307 data_list = self.__cell_data[col][row]
2308 except KeyError:
2309 continue
2310
2311 if len(data_list) == 1:
2312 data.append(data_list[0])
2313 continue
2314
2315 if exclude_multi_cells:
2316 gmDispatcher.send(signal = 'statustext', msg = _('Excluding multi-result field from further processing.'))
2317 continue
2318
2319 if auto_include_multi_cells:
2320 data.extend(data_list)
2321 continue
2322
2323 data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list)
2324 if data_to_include is None:
2325 continue
2326 data.extend(data_to_include)
2327
2328 return data
2329
2330
2332 data = gmListWidgets.get_choices_from_list (
2333 parent = self,
2334 msg = _(
2335 'Your selection includes a field with multiple results.\n'
2336 '\n'
2337 'Please select the individual results you want to work on:'
2338 ),
2339 caption = _('Selecting test results'),
2340 choices = [ [d['clin_when'], '%s: %s' % (d['abbrev_tt'], d['name_tt']), d['unified_val']] for d in cell_data ],
2341 columns = [ _('Date / Time'), _('Test'), _('Result') ],
2342 data = cell_data,
2343 single_selection = single_selection
2344 )
2345 return data
2346
2347
2348
2349
2351
2352 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
2353 self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels)
2354
2355
2356
2357 self.Bind(wx.EVT_SIZE, self.__resize_corner_window)
2358
2359
2360 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
2361
2362
2364 col = evt.GetCol()
2365 row = evt.GetRow()
2366
2367 try:
2368 self.__cell_data[col][row]
2369 except KeyError:
2370 presets = {}
2371 col_date = self.__col_label_data[col]
2372 presets['clin_when'] = {'data': col_date}
2373 test_type = self.__row_label_data[row]
2374 temporally_closest_result_of_row_type = test_type.meta_test_type.get_temporally_closest_result(col_date, self.__patient.ID)
2375 if temporally_closest_result_of_row_type is not None:
2376 presets['pk_test_type'] = {'data': temporally_closest_result_of_row_type['pk_test_type']}
2377 same_day_results = gmPathLab.get_results_for_day (
2378 timestamp = col_date,
2379 patient = self.__patient.ID,
2380 order_by = None
2381 )
2382 if len(same_day_results) > 0:
2383 presets['pk_episode'] = {'data': same_day_results[0]['pk_episode']}
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393 edit_measurement (
2394 parent = self,
2395 measurement = None,
2396 single_entry = True,
2397 presets = presets
2398 )
2399 return
2400
2401 if len(self.__cell_data[col][row]) > 1:
2402 data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True)
2403 else:
2404 data = self.__cell_data[col][row][0]
2405
2406 if data is None:
2407 return
2408
2409 edit_measurement(parent = self, measurement = data, single_entry = True)
2410
2411
2412
2413
2414
2415
2416
2417
2419
2420
2421
2422 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2423
2424 row = self.YToRow(y)
2425
2426 if self.__prev_label_row == row:
2427 return
2428
2429 self.__prev_label_row == row
2430
2431 evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row))
2432
2433
2434
2435
2436
2437
2438
2439
2441 """Calculate where the mouse is and set the tooltip dynamically."""
2442
2443
2444
2445 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459 row, col = self.XYToCell(x, y)
2460
2461 if (row == self.__prev_row) and (col == self.__prev_col):
2462 return
2463
2464 self.__prev_row = row
2465 self.__prev_col = col
2466
2467 evt.GetEventObject().SetToolTip(self.get_cell_tooltip(col=col, row=row))
2468
2469
2470
2471
2473 return self.__patient
2474
2478
2479 patient = property(_get_patient, _set_patient)
2480
2484
2485 panel_to_show = property(lambda x:x, _set_panel_to_show)
2486
2490
2491 show_by_panel = property(lambda x:x, _set_show_by_panel)
2492
2493
2494
2495
2496 from Gnumed.wxGladeWidgets import wxgMeasurementsPnl
2497
2498 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
2499 """Panel holding a grid with lab data. Used as notebook page."""
2500
2508
2509
2510
2512 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
2513 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
2514 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._schedule_data_reget)
2515 gmDispatcher.connect(signal = 'clin.reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
2516
2518 self._schedule_data_reget()
2519
2521 self._GRID_results_all.patient = None
2522 self._GRID_results_battery.patient = None
2523
2526
2530
2534
2537
2543
2546
2566
2569
2572
2575
2577 wx.CallAfter(self.__on_panel_selected, panel=panel)
2578
2580 if panel is None:
2581 self._TCTRL_panel_comment.SetValue('')
2582 self._GRID_results_battery.panel_to_show = None
2583
2584 self._PNL_results_battery_grid.Hide()
2585 else:
2586 pnl = self._PRW_panel.GetData(as_instance = True)
2587 self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
2588 pnl['comment'],
2589 ''
2590 ))
2591 self._GRID_results_battery.panel_to_show = pnl
2592
2593 self._PNL_results_battery_grid.Show()
2594 self._GRID_results_battery.Fit()
2595 self._GRID_results_all.Fit()
2596 self.Layout()
2597
2599 wx.CallAfter(self.__on_panel_selection_modified)
2600
2602 self._TCTRL_panel_comment.SetValue('')
2603 if self._PRW_panel.GetValue().strip() == '':
2604 self._GRID_results_battery.panel_to_show = None
2605
2606 self._PNL_results_battery_grid.Hide()
2607 self.Layout()
2608
2609
2610
2612 self.SetMinSize((10, 10))
2613
2614 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
2615
2616 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
2617 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
2618
2619 item = self.__action_button_popup.Append(-1, _('Plot'))
2620 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
2621
2622 item = self.__action_button_popup.Append(-1, _('Export to &file'))
2623 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item)
2624 self.__action_button_popup.Enable(id = menu_id, enable = False)
2625
2626 item = self.__action_button_popup.Append(-1, _('Export to &clipboard'))
2627 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item)
2628 self.__action_button_popup.Enable(id = menu_id, enable = False)
2629
2630 item = self.__action_button_popup.Append(-1, _('&Delete'))
2631 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
2632
2633
2634
2635
2636 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
2637 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
2638
2639 self._GRID_results_battery.show_by_panel = True
2640 self._GRID_results_battery.panel_to_show = None
2641
2642 self._PNL_results_battery_grid.Hide()
2643 self._BTN_display_mode.SetLabel(_('All: by &Day'))
2644
2645 self._PNL_results_all_grid.Show()
2646 self._PNL_results_all_listed.Hide()
2647 self.Layout()
2648
2649 self._PRW_panel.SetFocus()
2650
2651
2652
2654 pat = gmPerson.gmCurrentPatient()
2655 if pat.connected:
2656 self._GRID_results_battery.patient = pat
2657 if self.__display_mode == 'grid':
2658 self._GRID_results_all.patient = pat
2659 self._PNL_results_all_listed.patient = None
2660 else:
2661 self._GRID_results_all.patient = None
2662 self._PNL_results_all_listed.patient = pat
2663 else:
2664 self._GRID_results_battery.patient = None
2665 self._GRID_results_all.patient = None
2666 self._PNL_results_all_listed.patient = None
2667 return True
2668
2669
2670
2671
2673
2674 if tests is None:
2675 return True
2676
2677 if len(tests) == 0:
2678 return True
2679
2680 if parent is None:
2681 parent = wx.GetApp().GetTopWindow()
2682
2683 if len(tests) > 10:
2684 test_count = len(tests)
2685 tests2show = None
2686 else:
2687 test_count = None
2688 tests2show = tests
2689 if len(tests) == 0:
2690 return True
2691
2692 dlg = cMeasurementsReviewDlg(parent, -1, tests = tests, test_count = test_count)
2693 decision = dlg.ShowModal()
2694 if decision != wx.ID_APPLY:
2695 return True
2696
2697 wx.BeginBusyCursor()
2698 if dlg._RBTN_confirm_abnormal.GetValue():
2699 abnormal = None
2700 elif dlg._RBTN_results_normal.GetValue():
2701 abnormal = False
2702 else:
2703 abnormal = True
2704
2705 if dlg._RBTN_confirm_relevance.GetValue():
2706 relevant = None
2707 elif dlg._RBTN_results_not_relevant.GetValue():
2708 relevant = False
2709 else:
2710 relevant = True
2711
2712 comment = None
2713 if len(tests) == 1:
2714 comment = dlg._TCTRL_comment.GetValue()
2715
2716 make_responsible = dlg._CHBOX_responsible.IsChecked()
2717 dlg.DestroyLater()
2718
2719 for test in tests:
2720 test.set_review (
2721 technically_abnormal = abnormal,
2722 clinically_relevant = relevant,
2723 comment = comment,
2724 make_me_responsible = make_responsible
2725 )
2726 wx.EndBusyCursor()
2727
2728 return True
2729
2730
2731 from Gnumed.wxGladeWidgets import wxgMeasurementsReviewDlg
2732
2734
2736
2737 try:
2738 tests = kwargs['tests']
2739 del kwargs['tests']
2740 test_count = len(tests)
2741 try: del kwargs['test_count']
2742 except KeyError: pass
2743 except KeyError:
2744 tests = None
2745 test_count = kwargs['test_count']
2746 del kwargs['test_count']
2747
2748 wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs)
2749
2750 if tests is None:
2751 msg = _('%s results selected. Too many to list individually.') % test_count
2752 else:
2753 msg = '\n'.join (
2754 [ '%s: %s %s (%s)' % (
2755 t['unified_abbrev'],
2756 t['unified_val'],
2757 t['val_unit'],
2758 gmDateTime.pydt_strftime(t['clin_when'], '%Y %b %d')
2759 ) for t in tests
2760 ]
2761 )
2762
2763 self._LBL_tests.SetLabel(msg)
2764
2765 if test_count == 1:
2766 self._TCTRL_comment.Enable(True)
2767 self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], ''))
2768 if tests[0]['you_are_responsible']:
2769 self._CHBOX_responsible.Enable(False)
2770
2771 self.Fit()
2772
2773
2774
2780
2781
2782 from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl
2783
2784 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
2785 """This edit area saves *new* measurements into the active patient only."""
2786
2803
2804
2805
2806
2808 try:
2809 self._PRW_test.SetData(data = fields['pk_test_type']['data'])
2810 except KeyError:
2811 pass
2812 try:
2813 self._DPRW_evaluated.SetData(data = fields['clin_when']['data'])
2814 except KeyError:
2815 pass
2816 try:
2817 self._PRW_problem.SetData(data = fields['pk_episode']['data'])
2818 except KeyError:
2819 pass
2820 try:
2821 self._PRW_units.SetText(fields['val_unit']['data'], fields['val_unit']['data'], True)
2822 except KeyError:
2823 pass
2824 try:
2825 self._TCTRL_normal_min.SetValue(fields['val_normal_min']['data'])
2826 except KeyError:
2827 pass
2828 try:
2829 self._TCTRL_normal_max.SetValue(fields['val_normal_max']['data'])
2830 except KeyError:
2831 pass
2832 try:
2833 self._TCTRL_normal_range.SetValue(fields['val_normal_range']['data'])
2834 except KeyError:
2835 pass
2836 try:
2837 self._TCTRL_target_min.SetValue(fields['val_target_min']['data'])
2838 except KeyError:
2839 pass
2840 try:
2841 self._TCTRL_target_max.SetValue(fields['val_target_max']['data'])
2842 except KeyError:
2843 pass
2844 try:
2845 self._TCTRL_target_range.SetValue(fields['val_target_range']['data'])
2846 except KeyError:
2847 pass
2848
2849 self._TCTRL_result.SetFocus()
2850
2851
2883
2885 self._PRW_test.SetData(data = self.data['pk_test_type'])
2886 self.__refresh_loinc_info()
2887 self.__refresh_previous_value()
2888 self.__update_units_context()
2889 self._TCTRL_result.SetValue(self.data['unified_val'])
2890 self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True)
2891 self._PRW_abnormality_indicator.SetText (
2892 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2893 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2894 True
2895 )
2896 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2897 self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], ''))
2898 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2899 self._PRW_problem.SetData(self.data['pk_episode'])
2900 self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], ''))
2901 self._CHBOX_review.SetValue(False)
2902 self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False))
2903 self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False))
2904 self._CHBOX_abnormal.Enable(False)
2905 self._CHBOX_relevant.Enable(False)
2906 self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], ''))
2907 self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(self.data['val_normal_min'], '')))
2908 self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(self.data['val_normal_max'], '')))
2909 self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], ''))
2910 self._TCTRL_target_min.SetValue(str(gmTools.coalesce(self.data['val_target_min'], '')))
2911 self._TCTRL_target_max.SetValue(str(gmTools.coalesce(self.data['val_target_max'], '')))
2912 self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], ''))
2913 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], ''))
2914
2915 self._TCTRL_result.SetFocus()
2916
2918 self._PRW_test.SetText('', None, True)
2919 self.__refresh_loinc_info()
2920 self.__refresh_previous_value()
2921 self.__update_units_context()
2922 self._TCTRL_result.SetValue('')
2923 self._PRW_units.SetText('', None, True)
2924 self._PRW_abnormality_indicator.SetText('', None, True)
2925 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2926 self._TCTRL_note_test_org.SetValue('')
2927 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2928 self._PRW_problem.SetData(self.data['pk_episode'])
2929 self._TCTRL_narrative.SetValue('')
2930 self._CHBOX_review.SetValue(False)
2931 self._CHBOX_abnormal.SetValue(False)
2932 self._CHBOX_relevant.SetValue(False)
2933 self._CHBOX_abnormal.Enable(False)
2934 self._CHBOX_relevant.Enable(False)
2935 self._TCTRL_review_comment.SetValue('')
2936 self._TCTRL_normal_min.SetValue('')
2937 self._TCTRL_normal_max.SetValue('')
2938 self._TCTRL_normal_range.SetValue('')
2939 self._TCTRL_target_min.SetValue('')
2940 self._TCTRL_target_max.SetValue('')
2941 self._TCTRL_target_range.SetValue('')
2942 self._TCTRL_norm_ref_group.SetValue('')
2943
2944 self._PRW_test.SetFocus()
2945
2947
2948 validity = True
2949
2950 if not self._DPRW_evaluated.is_valid_timestamp():
2951 self._DPRW_evaluated.display_as_valid(False)
2952 validity = False
2953 else:
2954 self._DPRW_evaluated.display_as_valid(True)
2955
2956 val = self._TCTRL_result.GetValue().strip()
2957 if val == '':
2958 validity = False
2959 self.display_ctrl_as_valid(self._TCTRL_result, False)
2960 else:
2961 self.display_ctrl_as_valid(self._TCTRL_result, True)
2962 numeric, val = gmTools.input2decimal(val)
2963 if numeric:
2964 if self._PRW_units.GetValue().strip() == '':
2965 self._PRW_units.display_as_valid(False)
2966 validity = False
2967 else:
2968 self._PRW_units.display_as_valid(True)
2969 else:
2970 self._PRW_units.display_as_valid(True)
2971
2972 if self._PRW_problem.GetValue().strip() == '':
2973 self._PRW_problem.display_as_valid(False)
2974 validity = False
2975 else:
2976 self._PRW_problem.display_as_valid(True)
2977
2978 if self._PRW_test.GetValue().strip() == '':
2979 self._PRW_test.display_as_valid(False)
2980 validity = False
2981 else:
2982 self._PRW_test.display_as_valid(True)
2983
2984 if self._PRW_intended_reviewer.GetData() is None:
2985 self._PRW_intended_reviewer.display_as_valid(False)
2986 validity = False
2987 else:
2988 self._PRW_intended_reviewer.display_as_valid(True)
2989
2990 ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max]
2991 for widget in ctrls:
2992 val = widget.GetValue().strip()
2993 if val == '':
2994 continue
2995 try:
2996 decimal.Decimal(val.replace(',', '.', 1))
2997 self.display_ctrl_as_valid(widget, True)
2998 except:
2999 validity = False
3000 self.display_ctrl_as_valid(widget, False)
3001
3002 if validity is False:
3003 self.StatusText = _('Cannot save result. Invalid or missing essential input.')
3004
3005 return validity
3006
3008
3009 emr = gmPerson.gmCurrentPatient().emr
3010
3011 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
3012 if success:
3013 v_num = result
3014 v_al = None
3015 else:
3016 v_al = self._TCTRL_result.GetValue().strip()
3017 v_num = None
3018
3019 pk_type = self._PRW_test.GetData()
3020 if pk_type is None:
3021 abbrev = self._PRW_test.GetValue().strip()
3022 name = self._PRW_test.GetValue().strip()
3023 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3024 lab = manage_measurement_orgs (
3025 parent = self,
3026 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
3027 )
3028 if lab is not None:
3029 lab = lab['pk_test_org']
3030 tt = gmPathLab.create_measurement_type (
3031 lab = lab,
3032 abbrev = abbrev,
3033 name = name,
3034 unit = unit
3035 )
3036 pk_type = tt['pk_test_type']
3037
3038 tr = emr.add_test_result (
3039 episode = self._PRW_problem.GetData(can_create=True, is_open=False),
3040 type = pk_type,
3041 intended_reviewer = self._PRW_intended_reviewer.GetData(),
3042 val_num = v_num,
3043 val_alpha = v_al,
3044 unit = self._PRW_units.GetValue()
3045 )
3046
3047 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
3048
3049 ctrls = [
3050 ('abnormality_indicator', self._PRW_abnormality_indicator),
3051 ('note_test_org', self._TCTRL_note_test_org),
3052 ('comment', self._TCTRL_narrative),
3053 ('val_normal_range', self._TCTRL_normal_range),
3054 ('val_target_range', self._TCTRL_target_range),
3055 ('norm_ref_group', self._TCTRL_norm_ref_group)
3056 ]
3057 for field, widget in ctrls:
3058 tr[field] = widget.GetValue().strip()
3059
3060 ctrls = [
3061 ('val_normal_min', self._TCTRL_normal_min),
3062 ('val_normal_max', self._TCTRL_normal_max),
3063 ('val_target_min', self._TCTRL_target_min),
3064 ('val_target_max', self._TCTRL_target_max)
3065 ]
3066 for field, widget in ctrls:
3067 val = widget.GetValue().strip()
3068 if val == '':
3069 tr[field] = None
3070 else:
3071 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
3072
3073 tr.save_payload()
3074
3075 if self._CHBOX_review.GetValue() is True:
3076 tr.set_review (
3077 technically_abnormal = self._CHBOX_abnormal.GetValue(),
3078 clinically_relevant = self._CHBOX_relevant.GetValue(),
3079 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
3080 make_me_responsible = False
3081 )
3082
3083 self.data = tr
3084
3085
3086
3087
3088
3089
3090
3091
3092 return True
3093
3095
3096 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
3097 if success:
3098 v_num = result
3099 v_al = None
3100 else:
3101 v_num = None
3102 v_al = self._TCTRL_result.GetValue().strip()
3103
3104 pk_type = self._PRW_test.GetData()
3105 if pk_type is None:
3106 abbrev = self._PRW_test.GetValue().strip()
3107 name = self._PRW_test.GetValue().strip()
3108 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3109 lab = manage_measurement_orgs (
3110 parent = self,
3111 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
3112 )
3113 if lab is not None:
3114 lab = lab['pk_test_org']
3115 tt = gmPathLab.create_measurement_type (
3116 lab = None,
3117 abbrev = abbrev,
3118 name = name,
3119 unit = unit
3120 )
3121 pk_type = tt['pk_test_type']
3122
3123 tr = self.data
3124
3125 tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False)
3126 tr['pk_test_type'] = pk_type
3127 tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData()
3128 tr['val_num'] = v_num
3129 tr['val_alpha'] = v_al
3130 tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3131 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
3132
3133 ctrls = [
3134 ('abnormality_indicator', self._PRW_abnormality_indicator),
3135 ('note_test_org', self._TCTRL_note_test_org),
3136 ('comment', self._TCTRL_narrative),
3137 ('val_normal_range', self._TCTRL_normal_range),
3138 ('val_target_range', self._TCTRL_target_range),
3139 ('norm_ref_group', self._TCTRL_norm_ref_group)
3140 ]
3141 for field, widget in ctrls:
3142 tr[field] = widget.GetValue().strip()
3143
3144 ctrls = [
3145 ('val_normal_min', self._TCTRL_normal_min),
3146 ('val_normal_max', self._TCTRL_normal_max),
3147 ('val_target_min', self._TCTRL_target_min),
3148 ('val_target_max', self._TCTRL_target_max)
3149 ]
3150 for field, widget in ctrls:
3151 val = widget.GetValue().strip()
3152 if val == '':
3153 tr[field] = None
3154 else:
3155 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
3156
3157 tr.save_payload()
3158
3159 if self._CHBOX_review.GetValue() is True:
3160 tr.set_review (
3161 technically_abnormal = self._CHBOX_abnormal.GetValue(),
3162 clinically_relevant = self._CHBOX_relevant.GetValue(),
3163 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
3164 make_me_responsible = False
3165 )
3166
3167
3168
3169
3170
3171
3172
3173
3174 return True
3175
3176
3177
3182
3184 self.__refresh_loinc_info()
3185 self.__refresh_previous_value()
3186 self.__update_units_context()
3187
3188 self.__update_normal_range()
3189 self.__update_clinical_range()
3190
3192
3193 self.__update_normal_range()
3194 self.__update_clinical_range()
3195
3197
3198 if not self._CHBOX_review.GetValue():
3199 self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != '')
3200
3205
3221
3225
3226
3227
3229
3230 if self._PRW_test.GetData() is None:
3231 self._PRW_units.unset_context(context = 'pk_type')
3232 self._PRW_units.unset_context(context = 'loinc')
3233 if self._PRW_test.GetValue().strip() == '':
3234 self._PRW_units.unset_context(context = 'test_name')
3235 else:
3236 self._PRW_units.set_context(context = 'test_name', val = self._PRW_test.GetValue().strip())
3237 return
3238
3239 tt = self._PRW_test.GetData(as_instance = True)
3240
3241 self._PRW_units.set_context(context = 'pk_type', val = tt['pk_test_type'])
3242 self._PRW_units.set_context(context = 'test_name', val = tt['name'])
3243
3244 if tt['loinc'] is not None:
3245 self._PRW_units.set_context(context = 'loinc', val = tt['loinc'])
3246
3247
3248 if self._PRW_units.GetValue().strip() == '':
3249 clin_when = self._DPRW_evaluated.GetData()
3250 if clin_when is None:
3251 unit = tt.temporally_closest_unit
3252 else:
3253 clin_when = clin_when.get_pydt()
3254 unit = tt.get_temporally_closest_unit(timestamp = clin_when)
3255 if unit is None:
3256 self._PRW_units.SetText('', unit, True)
3257 else:
3258 self._PRW_units.SetText(unit, unit, True)
3259
3260
3281
3282
3303
3304
3306
3307 self._TCTRL_loinc.SetValue('')
3308
3309 if self._PRW_test.GetData() is None:
3310 return
3311
3312 tt = self._PRW_test.GetData(as_instance = True)
3313
3314 if tt['loinc'] is None:
3315 return
3316
3317 info = gmLOINC.loinc2term(loinc = tt['loinc'])
3318 if len(info) == 0:
3319 self._TCTRL_loinc.SetValue('')
3320 return
3321
3322 self._TCTRL_loinc.SetValue('%s: %s' % (tt['loinc'], info[0]))
3323
3325 self._TCTRL_previous_value.SetValue('')
3326
3327
3328 if self.data is not None:
3329 return
3330 if self._PRW_test.GetData() is None:
3331 return
3332 tt = self._PRW_test.GetData(as_instance = True)
3333 most_recent = tt.get_most_recent_results (
3334 no_of_results = 1,
3335 patient = gmPerson.gmCurrentPatient().ID
3336 )
3337 if most_recent is None:
3338 return
3339 self._TCTRL_previous_value.SetValue(_('%s ago: %s%s%s - %s%s') % (
3340 gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - most_recent['clin_when']),
3341 most_recent['unified_val'],
3342 most_recent['val_unit'],
3343 gmTools.coalesce(most_recent['abnormality_indicator'], '', ' (%s)'),
3344 most_recent['abbrev_tt'],
3345 gmTools.coalesce(most_recent.formatted_range, '', ' [%s]')
3346 ))
3347 self._TCTRL_previous_value.SetToolTip(most_recent.format (
3348 with_review = True,
3349 with_evaluation = False,
3350 with_ranges = True,
3351 with_episode = True,
3352 with_type_details=True
3353 ))
3354
3355
3356
3357
3359
3360 if parent is None:
3361 parent = wx.GetApp().GetTopWindow()
3362
3363 if msg is None:
3364 msg = _('Pick the relevant measurement types.')
3365
3366 if right_column is None:
3367 right_columns = [_('Picked')]
3368 else:
3369 right_columns = [right_column]
3370
3371 picker = gmListWidgets.cItemPickerDlg(parent, -1, msg = msg)
3372 picker.set_columns(columns = [_('Known measurement types')], columns_right = right_columns)
3373 types = gmPathLab.get_measurement_types(order_by = 'unified_abbrev')
3374 picker.set_choices (
3375 choices = [
3376 '%s: %s%s' % (
3377 t['unified_abbrev'],
3378 t['unified_name'],
3379 gmTools.coalesce(t['name_org'], '', ' (%s)')
3380 )
3381 for t in types
3382 ],
3383 data = types
3384 )
3385 if picks is not None:
3386 picker.set_picks (
3387 picks = [
3388 '%s: %s%s' % (
3389 p['unified_abbrev'],
3390 p['unified_name'],
3391 gmTools.coalesce(p['name_org'], '', ' (%s)')
3392 )
3393 for p in picks
3394 ],
3395 data = picks
3396 )
3397 result = picker.ShowModal()
3398
3399 if result == wx.ID_CANCEL:
3400 picker.DestroyLater()
3401 return None
3402
3403 picks = picker.picks
3404 picker.DestroyLater()
3405 return picks
3406
3407
3430
3431
3432 def delete(measurement_type):
3433 if measurement_type.in_use:
3434 gmDispatcher.send (
3435 signal = 'statustext',
3436 beep = True,
3437 msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev'])
3438 )
3439 return False
3440 gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type'])
3441 return True
3442
3443
3444 def get_tooltip(test_type):
3445 return test_type.format()
3446
3447
3448 def manage_aggregates(test_type):
3449 manage_meta_test_types(parent = parent)
3450 return False
3451
3452
3453 def manage_panels_of_type(test_type):
3454 if test_type['loinc'] is None:
3455 return False
3456 all_panels = gmPathLab.get_test_panels(order_by = 'description')
3457 curr_panels = test_type.test_panels
3458 if curr_panels is None:
3459 curr_panels = []
3460 panel_candidates = [ p for p in all_panels if p['pk_test_panel'] not in [
3461 c_pnl['pk_test_panel'] for c_pnl in curr_panels
3462 ] ]
3463 picker = gmListWidgets.cItemPickerDlg(parent, -1, title = 'Panels with [%s]' % test_type['abbrev'])
3464 picker.set_columns(['Panels available'], ['Panels [%s] is to be on' % test_type['abbrev']])
3465 picker.set_choices (
3466 choices = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in panel_candidates ],
3467 data = panel_candidates
3468 )
3469 picker.set_picks (
3470 picks = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in curr_panels ],
3471 data = curr_panels
3472 )
3473 exit_type = picker.ShowModal()
3474 if exit_type == wx.ID_CANCEL:
3475 return False
3476
3477
3478 panels2add = [ p for p in picker.picks if p['pk_test_panel'] not in [
3479 c_pnl['pk_test_panel'] for c_pnl in curr_panels
3480 ] ]
3481
3482 panels2remove = [ p for p in curr_panels if p['pk_test_panel'] not in [
3483 picked_pnl['pk_test_panel'] for picked_pnl in picker.picks
3484 ] ]
3485 for new_panel in panels2add:
3486 new_panel.add_loinc(test_type['loinc'])
3487 for stale_panel in panels2remove:
3488 stale_panel.remove_loinc(test_type['loinc'])
3489
3490 return True
3491
3492
3493 def refresh(lctrl):
3494 mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev')
3495 items = [ [
3496 m['abbrev'],
3497 m['name'],
3498 gmTools.coalesce(m['reference_unit'], ''),
3499 gmTools.coalesce(m['loinc'], ''),
3500 gmTools.coalesce(m['comment_type'], ''),
3501 gmTools.coalesce(m['name_org'], '?'),
3502 gmTools.coalesce(m['comment_org'], ''),
3503 m['pk_test_type']
3504 ] for m in mtypes ]
3505 lctrl.set_string_items(items)
3506 lctrl.set_data(mtypes)
3507
3508
3509 gmListWidgets.get_choices_from_list (
3510 parent = parent,
3511 caption = _('Measurement types.'),
3512 columns = [ _('Abbrev'), _('Name'), _('Unit'), _('LOINC'), _('Comment'), _('Org'), _('Comment'), '#' ],
3513 single_selection = True,
3514 refresh_callback = refresh,
3515 edit_callback = edit,
3516 new_callback = edit,
3517 delete_callback = delete,
3518 list_tooltip_callback = get_tooltip,
3519 left_extra_button = (_('%s &Aggregate') % gmTools.u_sum, _('Manage aggregations (%s) of tests into groups.') % gmTools.u_sum, manage_aggregates),
3520 middle_extra_button = (_('Select panels'), _('Select panels the focussed test type is to belong to.'), manage_panels_of_type)
3521 )
3522
3523
3525
3527
3528 query = """
3529 SELECT DISTINCT ON (field_label)
3530 pk_test_type AS data,
3531 name
3532 || ' ('
3533 || coalesce (
3534 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
3535 '%(in_house)s'
3536 )
3537 || ')'
3538 AS field_label,
3539 name
3540 || ' ('
3541 || abbrev || ', '
3542 || coalesce(abbrev_meta || ': ' || name_meta || ', ', '')
3543 || coalesce (
3544 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
3545 '%(in_house)s'
3546 )
3547 || ')'
3548 AS list_label
3549 FROM
3550 clin.v_test_types c_vtt
3551 WHERE
3552 abbrev_meta %%(fragment_condition)s
3553 OR
3554 name_meta %%(fragment_condition)s
3555 OR
3556 abbrev %%(fragment_condition)s
3557 OR
3558 name %%(fragment_condition)s
3559 ORDER BY field_label
3560 LIMIT 50""" % {'in_house': _('generic / in house lab')}
3561
3562 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3563 mp.setThresholds(1, 2, 4)
3564 mp.word_separators = '[ \t:@]+'
3565 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
3566 self.matcher = mp
3567 self.SetToolTip(_('Select the type of measurement.'))
3568 self.selection_only = False
3569
3570
3576
3577
3578 from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl
3579
3580 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
3581
3598
3599
3601
3602
3603 query = """
3604 select distinct on (name)
3605 pk,
3606 name
3607 from clin.test_type
3608 where
3609 name %(fragment_condition)s
3610 order by name
3611 limit 50"""
3612 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3613 mp.setThresholds(1, 2, 4)
3614 self._PRW_name.matcher = mp
3615 self._PRW_name.selection_only = False
3616 self._PRW_name.add_callback_on_lose_focus(callback = self._on_name_lost_focus)
3617
3618
3619 query = """
3620 select distinct on (abbrev)
3621 pk,
3622 abbrev
3623 from clin.test_type
3624 where
3625 abbrev %(fragment_condition)s
3626 order by abbrev
3627 limit 50"""
3628 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3629 mp.setThresholds(1, 2, 3)
3630 self._PRW_abbrev.matcher = mp
3631 self._PRW_abbrev.selection_only = False
3632
3633
3634 self._PRW_reference_unit.selection_only = False
3635
3636
3637 mp = gmLOINC.cLOINCMatchProvider()
3638 mp.setThresholds(1, 2, 4)
3639
3640
3641 self._PRW_loinc.matcher = mp
3642 self._PRW_loinc.selection_only = False
3643 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
3644
3645
3647
3648 test = self._PRW_name.GetValue().strip()
3649
3650 if test == '':
3651 self._PRW_reference_unit.unset_context(context = 'test_name')
3652 return
3653
3654 self._PRW_reference_unit.set_context(context = 'test_name', val = test)
3655
3656
3658 loinc = self._PRW_loinc.GetData()
3659
3660 if loinc is None:
3661 self._TCTRL_loinc_info.SetValue('')
3662 self._PRW_reference_unit.unset_context(context = 'loinc')
3663 return
3664
3665 self._PRW_reference_unit.set_context(context = 'loinc', val = loinc)
3666
3667 info = gmLOINC.loinc2term(loinc = loinc)
3668 if len(info) == 0:
3669 self._TCTRL_loinc_info.SetValue('')
3670 return
3671
3672 self._TCTRL_loinc_info.SetValue(info[0])
3673
3674
3675
3676
3678
3679 has_errors = False
3680 for field in [self._PRW_name, self._PRW_abbrev, self._PRW_reference_unit]:
3681 if field.GetValue().strip() in ['', None]:
3682 has_errors = True
3683 field.display_as_valid(valid = False)
3684 else:
3685 field.display_as_valid(valid = True)
3686 field.Refresh()
3687
3688 return (not has_errors)
3689
3690
3720
3759
3760
3762 self._PRW_name.SetText('', None, True)
3763 self._on_name_lost_focus()
3764 self._PRW_abbrev.SetText('', None, True)
3765 self._PRW_reference_unit.SetText('', None, True)
3766 self._PRW_loinc.SetText('', None, True)
3767 self._on_loinc_lost_focus()
3768 self._TCTRL_comment_type.SetValue('')
3769 self._PRW_test_org.SetText('', None, True)
3770 self._PRW_meta_type.SetText('', None, True)
3771
3772 self._PRW_name.SetFocus()
3773
3775 self._PRW_name.SetText(self.data['name'], self.data['name'], True)
3776 self._on_name_lost_focus()
3777 self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True)
3778 self._PRW_reference_unit.SetText (
3779 gmTools.coalesce(self.data['reference_unit'], ''),
3780 self.data['reference_unit'],
3781 True
3782 )
3783 self._PRW_loinc.SetText (
3784 gmTools.coalesce(self.data['loinc'], ''),
3785 self.data['loinc'],
3786 True
3787 )
3788 self._on_loinc_lost_focus()
3789 self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], ''))
3790 self._PRW_test_org.SetText (
3791 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
3792 self.data['pk_test_org'],
3793 True
3794 )
3795 if self.data['pk_meta_test_type'] is None:
3796 self._PRW_meta_type.SetText('', None, True)
3797 else:
3798 self._PRW_meta_type.SetText('%s: %s' % (self.data['abbrev_meta'], self.data['name_meta']), self.data['pk_meta_test_type'], True)
3799
3800 self._PRW_name.SetFocus()
3801
3803 self._refresh_as_new()
3804 self._PRW_test_org.SetText (
3805 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
3806 self.data['pk_test_org'],
3807 True
3808 )
3809 self._PRW_name.SetFocus()
3810
3811
3812 _SQL_units_from_test_results = """
3813 -- via clin.v_test_results.pk_type (for types already used in results)
3814 SELECT
3815 val_unit AS data,
3816 val_unit AS field_label,
3817 val_unit || ' (' || name_tt || ')' AS list_label,
3818 1 AS rank
3819 FROM
3820 clin.v_test_results
3821 WHERE
3822 (
3823 val_unit %(fragment_condition)s
3824 OR
3825 reference_unit %(fragment_condition)s
3826 )
3827 %(ctxt_type_pk)s
3828 %(ctxt_test_name)s
3829 """
3830
3831 _SQL_units_from_test_types = """
3832 -- via clin.test_type (for types not yet used in results)
3833 SELECT
3834 reference_unit AS data,
3835 reference_unit AS field_label,
3836 reference_unit || ' (' || name || ')' AS list_label,
3837 2 AS rank
3838 FROM
3839 clin.test_type
3840 WHERE
3841 reference_unit %(fragment_condition)s
3842 %(ctxt_ctt)s
3843 """
3844
3845 _SQL_units_from_loinc_ipcc = """
3846 -- via ref.loinc.ipcc_units
3847 SELECT
3848 ipcc_units AS data,
3849 ipcc_units AS field_label,
3850 ipcc_units || ' (LOINC.ipcc: ' || term || ')' AS list_label,
3851 3 AS rank
3852 FROM
3853 ref.loinc
3854 WHERE
3855 ipcc_units %(fragment_condition)s
3856 %(ctxt_loinc)s
3857 %(ctxt_loinc_term)s
3858 """
3859
3860 _SQL_units_from_loinc_submitted = """
3861 -- via ref.loinc.submitted_units
3862 SELECT
3863 submitted_units AS data,
3864 submitted_units AS field_label,
3865 submitted_units || ' (LOINC.submitted:' || term || ')' AS list_label,
3866 3 AS rank
3867 FROM
3868 ref.loinc
3869 WHERE
3870 submitted_units %(fragment_condition)s
3871 %(ctxt_loinc)s
3872 %(ctxt_loinc_term)s
3873 """
3874
3875 _SQL_units_from_loinc_example = """
3876 -- via ref.loinc.example_units
3877 SELECT
3878 example_units AS data,
3879 example_units AS field_label,
3880 example_units || ' (LOINC.example: ' || term || ')' AS list_label,
3881 3 AS rank
3882 FROM
3883 ref.loinc
3884 WHERE
3885 example_units %(fragment_condition)s
3886 %(ctxt_loinc)s
3887 %(ctxt_loinc_term)s
3888 """
3889
3890 _SQL_units_from_substance_doses = """
3891 -- via ref.v_substance_doses.unit
3892 SELECT
3893 unit AS data,
3894 unit AS field_label,
3895 unit || ' (' || substance || ')' AS list_label,
3896 2 AS rank
3897 FROM
3898 ref.v_substance_doses
3899 WHERE
3900 unit %(fragment_condition)s
3901 %(ctxt_substance)s
3902 """
3903
3904 _SQL_units_from_substance_doses2 = """
3905 -- via ref.v_substance_doses.dose_unit
3906 SELECT
3907 dose_unit AS data,
3908 dose_unit AS field_label,
3909 dose_unit || ' (' || substance || ')' AS list_label,
3910 2 AS rank
3911 FROM
3912 ref.v_substance_doses
3913 WHERE
3914 dose_unit %(fragment_condition)s
3915 %(ctxt_substance)s
3916 """
3917
3918
3920
3922
3923 query = """
3924 SELECT DISTINCT ON (data)
3925 data,
3926 field_label,
3927 list_label
3928 FROM (
3929
3930 SELECT
3931 data,
3932 field_label,
3933 list_label,
3934 rank
3935 FROM (
3936 (%s) UNION ALL
3937 (%s) UNION ALL
3938 (%s) UNION ALL
3939 (%s) UNION ALL
3940 (%s) UNION ALL
3941 (%s) UNION ALL
3942 (%s)
3943 ) AS all_matching_units
3944 WHERE data IS NOT NULL
3945 ORDER BY rank, list_label
3946
3947 ) AS ranked_matching_units
3948 LIMIT 50""" % (
3949 _SQL_units_from_test_results,
3950 _SQL_units_from_test_types,
3951 _SQL_units_from_loinc_ipcc,
3952 _SQL_units_from_loinc_submitted,
3953 _SQL_units_from_loinc_example,
3954 _SQL_units_from_substance_doses,
3955 _SQL_units_from_substance_doses2
3956 )
3957
3958 ctxt = {
3959 'ctxt_type_pk': {
3960 'where_part': 'AND pk_test_type = %(pk_type)s',
3961 'placeholder': 'pk_type'
3962 },
3963 'ctxt_test_name': {
3964 'where_part': 'AND %(test_name)s IN (name_tt, name_meta, abbrev_meta)',
3965 'placeholder': 'test_name'
3966 },
3967 'ctxt_ctt': {
3968 'where_part': 'AND %(test_name)s IN (name, abbrev)',
3969 'placeholder': 'test_name'
3970 },
3971 'ctxt_loinc': {
3972 'where_part': 'AND code = %(loinc)s',
3973 'placeholder': 'loinc'
3974 },
3975 'ctxt_loinc_term': {
3976 'where_part': 'AND term ~* %(test_name)s',
3977 'placeholder': 'test_name'
3978 },
3979 'ctxt_substance': {
3980 'where_part': 'AND description ~* %(substance)s',
3981 'placeholder': 'substance'
3982 }
3983 }
3984
3985 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = ctxt)
3986 mp.setThresholds(1, 2, 4)
3987
3988 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
3989 self.matcher = mp
3990 self.SetToolTip(_('Select the desired unit for the amount or measurement.'))
3991 self.selection_only = False
3992 self.phrase_separators = '[;|]+'
3993
3994
3995
3996
3998
4000
4001 query = """
4002 select distinct abnormality_indicator,
4003 abnormality_indicator, abnormality_indicator
4004 from clin.v_test_results
4005 where
4006 abnormality_indicator %(fragment_condition)s
4007 order by abnormality_indicator
4008 limit 25"""
4009
4010 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4011 mp.setThresholds(1, 1, 2)
4012 mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"'
4013 mp.word_separators = '[ \t&:]+'
4014 gmPhraseWheel.cPhraseWheel.__init__ (
4015 self,
4016 *args,
4017 **kwargs
4018 )
4019 self.matcher = mp
4020 self.SetToolTip(_('Select an indicator for the level of abnormality.'))
4021 self.selection_only = False
4022
4023
4024
4025
4037
4046
4047 def refresh(lctrl):
4048 orgs = gmPathLab.get_test_orgs()
4049 lctrl.set_string_items ([
4050 (o['unit'], o['organization'], gmTools.coalesce(o['test_org_contact'], ''), gmTools.coalesce(o['comment'], ''), o['pk_test_org'])
4051 for o in orgs
4052 ])
4053 lctrl.set_data(orgs)
4054
4055 def delete(test_org):
4056 gmPathLab.delete_test_org(test_org = test_org['pk_test_org'])
4057 return True
4058
4059 if msg is None:
4060 msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n')
4061
4062 return gmListWidgets.get_choices_from_list (
4063 parent = parent,
4064 msg = msg,
4065 caption = _('Showing diagnostic orgs.'),
4066 columns = [_('Name'), _('Organization'), _('Contact'), _('Comment'), '#'],
4067 single_selection = True,
4068 refresh_callback = refresh,
4069 edit_callback = edit,
4070 new_callback = edit,
4071 delete_callback = delete
4072 )
4073
4074
4075 from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl
4076
4077 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
4078
4094
4095
4096
4097
4098
4099
4100
4101
4103 has_errors = False
4104 if self._PRW_org_unit.GetData() is None:
4105 if self._PRW_org_unit.GetValue().strip() == '':
4106 has_errors = True
4107 self._PRW_org_unit.display_as_valid(valid = False)
4108 else:
4109 self._PRW_org_unit.display_as_valid(valid = True)
4110 else:
4111 self._PRW_org_unit.display_as_valid(valid = True)
4112
4113 return (not has_errors)
4114
4125
4145
4150
4155
4157 self._refresh_as_new()
4158
4161
4162
4164
4166
4167 query = """
4168 SELECT DISTINCT ON (list_label)
4169 pk_test_org AS data,
4170 unit || ' (' || organization || ')' AS field_label,
4171 unit || ' @ ' || organization AS list_label
4172 FROM clin.v_test_orgs
4173 WHERE
4174 unit %(fragment_condition)s
4175 OR
4176 organization %(fragment_condition)s
4177 ORDER BY list_label
4178 LIMIT 50"""
4179 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4180 mp.setThresholds(1, 2, 4)
4181
4182 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4183 self.matcher = mp
4184 self.SetToolTip(_('The name of the path lab/diagnostic organisation.'))
4185 self.selection_only = False
4186
4199
4202
4203
4204
4205
4222
4223
4232
4233 def delete(meta_test_type):
4234 gmPathLab.delete_meta_type(meta_type = meta_test_type['pk'])
4235 return True
4236
4237 def get_tooltip(data):
4238 if data is None:
4239 return None
4240 return data.format(with_tests = True)
4241
4242 def refresh(lctrl):
4243 mtts = gmPathLab.get_meta_test_types()
4244 items = [ [
4245 m['abbrev'],
4246 m['name'],
4247 gmTools.coalesce(m['loinc'], ''),
4248 gmTools.coalesce(m['comment'], ''),
4249 m['pk']
4250 ] for m in mtts ]
4251 lctrl.set_string_items(items)
4252 lctrl.set_data(mtts)
4253
4254
4255 msg = _(
4256 '\n'
4257 'These are the meta test types currently defined in GNUmed.\n'
4258 '\n'
4259 'Meta test types allow you to aggregate several actual test types used\n'
4260 'by pathology labs into one logical type.\n'
4261 '\n'
4262 'This is useful for grouping together results of tests which come under\n'
4263 'different names but really are the same thing. This often happens when\n'
4264 'you switch labs or the lab starts using another test method.\n'
4265 )
4266
4267 gmListWidgets.get_choices_from_list (
4268 parent = parent,
4269 msg = msg,
4270 caption = _('Showing meta test types.'),
4271 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), '#'],
4272 single_selection = True,
4273 list_tooltip_callback = get_tooltip,
4274 edit_callback = edit,
4275 new_callback = edit,
4276 delete_callback = delete,
4277 refresh_callback = refresh
4278 )
4279
4280
4325
4326
4327 from Gnumed.wxGladeWidgets import wxgMetaTestTypeEAPnl
4328
4475
4476
4477
4478
4480 ea = cTestPanelEAPnl(parent, -1)
4481 ea.data = test_panel
4482 ea.mode = gmTools.coalesce(test_panel, 'new', 'edit')
4483 dlg = gmEditArea.cGenericEditAreaDlg2 (
4484 parent = parent,
4485 id = -1,
4486 edit_area = ea,
4487 single_entry = gmTools.bool2subst((test_panel is None), False, True)
4488 )
4489 dlg.SetTitle(gmTools.coalesce(test_panel, _('Adding new test panel'), _('Editing test panel')))
4490 if dlg.ShowModal() == wx.ID_OK:
4491 dlg.DestroyLater()
4492 return True
4493 dlg.DestroyLater()
4494 return False
4495
4496
4505
4506 def delete(test_panel):
4507 gmPathLab.delete_test_panel(pk = test_panel['pk_test_panel'])
4508 return True
4509
4510 def get_tooltip(test_panel):
4511 return test_panel.format()
4512
4513 def refresh(lctrl):
4514 panels = gmPathLab.get_test_panels(order_by = 'description')
4515 items = [ [
4516 p['description'],
4517 gmTools.coalesce(p['comment'], ''),
4518 p['pk_test_panel']
4519 ] for p in panels ]
4520 lctrl.set_string_items(items)
4521 lctrl.set_data(panels)
4522
4523 gmListWidgets.get_choices_from_list (
4524 parent = parent,
4525 caption = 'GNUmed: ' + _('Test panels list'),
4526 columns = [ _('Name'), _('Comment'), '#' ],
4527 single_selection = True,
4528 refresh_callback = refresh,
4529 edit_callback = edit,
4530 new_callback = edit,
4531 delete_callback = delete,
4532 list_tooltip_callback = get_tooltip
4533 )
4534
4535
4537
4539 query = """
4540 SELECT
4541 pk_test_panel
4542 AS data,
4543 description
4544 AS field_label,
4545 description
4546 AS list_label
4547 FROM
4548 clin.v_test_panels
4549 WHERE
4550 description %(fragment_condition)s
4551 ORDER BY field_label
4552 LIMIT 30"""
4553 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4554 mp.setThresholds(1, 2, 4)
4555
4556 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4557 self.matcher = mp
4558 self.SetToolTip(_('Select a test panel.'))
4559 self.selection_only = True
4560
4565
4570
4571
4572 from Gnumed.wxGladeWidgets import wxgTestPanelEAPnl
4573
4574 -class cTestPanelEAPnl(wxgTestPanelEAPnl.wxgTestPanelEAPnl, gmEditArea.cGenericEditAreaMixin):
4575
4595
4596
4598 self._LCTRL_loincs.set_columns([_('LOINC'), _('Term'), _('Units')])
4599 self._LCTRL_loincs.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
4600
4601 self._LCTRL_loincs.delete_callback = self._remove_loincs_from_list
4602 self.__refresh_loinc_list()
4603
4604 self._PRW_loinc.final_regex = r'.*'
4605 self._PRW_loinc.add_callback_on_selection(callback = self._on_loinc_selected)
4606
4607
4609 self._LCTRL_loincs.remove_items_safely()
4610 if self.__loincs is None:
4611 if self.data is None:
4612 return
4613 self.__loincs = self.data['loincs']
4614
4615 items = []
4616 for loinc in self.__loincs:
4617 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4618 if loinc_detail is None:
4619
4620 ttypes = gmPathLab.get_measurement_types(loincs = [loinc])
4621 if len(ttypes) == 0:
4622 items.append([loinc, _('LOINC not found'), ''])
4623 else:
4624 for tt in ttypes:
4625 items.append([loinc, _('not a LOINC') + u'; %(name)s @ %(name_org)s [#%(pk_test_type)s]' % tt, ''])
4626 continue
4627 items.append ([
4628 loinc,
4629 loinc_detail['term'],
4630 gmTools.coalesce(loinc_detail['example_units'], '', '%s')
4631 ])
4632
4633 self._LCTRL_loincs.set_string_items(items)
4634 self._LCTRL_loincs.set_column_widths()
4635
4636
4637
4638
4640 validity = True
4641
4642 if self.__loincs is None:
4643 if self.data is not None:
4644 self.__loincs = self.data['loincs']
4645
4646 if self.__loincs is None:
4647
4648 self.StatusText = _('No LOINC codes selected.')
4649 self._PRW_loinc.SetFocus()
4650
4651 if self._TCTRL_description.GetValue().strip() == '':
4652 validity = False
4653 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False)
4654 self._TCTRL_description.SetFocus()
4655 else:
4656 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True)
4657
4658 return validity
4659
4660
4669
4670
4672 self.data['description'] = self._TCTRL_description.GetValue().strip()
4673 self.data['comment'] = self._TCTRL_comment.GetValue().strip()
4674 self.data.save()
4675 if self.__loincs is not None:
4676 self.data.included_loincs = self.__loincs
4677 return True
4678
4679
4681 self._TCTRL_description.SetValue('')
4682 self._TCTRL_comment.SetValue('')
4683 self._PRW_loinc.SetText('', None)
4684 self._LBL_loinc.SetLabel('')
4685 self.__loincs = None
4686 self.__refresh_loinc_list()
4687
4688 self._TCTRL_description.SetFocus()
4689
4690
4692 self._refresh_as_new()
4693
4694
4696 self._TCTRL_description.SetValue(self.data['description'])
4697 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
4698 self._PRW_loinc.SetText('', None)
4699 self._LBL_loinc.SetLabel('')
4700 self.__loincs = self.data['loincs']
4701 self.__refresh_loinc_list()
4702
4703 self._PRW_loinc.SetFocus()
4704
4705
4706
4707
4709 loinc = self._PRW_loinc.GetData()
4710 if loinc is None:
4711 self._LBL_loinc.SetLabel('')
4712 return
4713 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4714 if loinc_detail is None:
4715 loinc_str = _('no LOINC details found')
4716 else:
4717 loinc_str = '%s: %s%s' % (
4718 loinc,
4719 loinc_detail['term'],
4720 gmTools.coalesce(loinc_detail['example_units'], '', ' (%s)')
4721 )
4722 self._LBL_loinc.SetLabel(loinc_str)
4723
4724
4746
4747
4751
4752
4754 loincs2remove = self._LCTRL_loincs.selected_item_data
4755 if loincs2remove is None:
4756 return
4757 for loinc in loincs2remove:
4758 try:
4759 while True:
4760 self.__loincs.remove(loinc[0])
4761 except ValueError:
4762 pass
4763 self.__refresh_loinc_list()
4764
4765
4766
4767
4768 if __name__ == '__main__':
4769
4770 from Gnumed.pycommon import gmLog2
4771 from Gnumed.wxpython import gmPatSearchWidgets
4772
4773 gmI18N.activate_locale()
4774 gmI18N.install_domain()
4775 gmDateTime.init()
4776
4777
4785
4793
4794
4795
4796
4797
4798
4799
4800 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
4801
4802 test_test_ea_pnl()
4803
4804
4805
4806