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.Destroy()
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.Destroy()
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.Destroy()
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, fields=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
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
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
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 most recent measurement results, possibly filtered by panel/battery.
1327
1328 - operates on a cPatient instance handed to it and NOT on the currently active patient
1329 """
1339
1340
1341
1342
1344 self._LCTRL_results.set_columns([_('Test'), _('Result'), _('When'), _('Range')])
1345 self._CHBOX_show_missing.Disable()
1346 self._PNL_related_documents.lab_reference = None
1347
1348
1357
1358
1360
1361 self._TCTRL_details.SetValue('')
1362 self._PNL_related_documents.lab_reference = None
1363
1364 pnl = self._PRW_panel.GetData(as_instance = True)
1365 if pnl is None:
1366 results = gmPathLab.get_most_recent_result_for_test_types(pk_patient = self.__patient.ID)
1367 else:
1368 results = pnl.get_most_recent_results (
1369 pk_patient = self.__patient.ID,
1370
1371 group_by_meta_type = True,
1372 include_missing = self._CHBOX_show_missing.IsChecked()
1373 )
1374 items = []
1375 data = []
1376 for r in results:
1377 if isinstance(r, gmPathLab.cTestResult):
1378 result_type = r['abbrev_tt']
1379 review = gmTools.bool2subst (
1380 r['reviewed'],
1381 '',
1382 ' ' + gmTools.u_writing_hand,
1383 ' ' + gmTools.u_writing_hand
1384 )
1385 result_val = '%s%s%s%s' % (
1386 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1387 gmTools.coalesce(r['val_unit'], '', ' %s'),
1388 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1389 review
1390 )
1391 result_when = _('%s ago (%s)') % (
1392 gmDateTime.format_interval_medically(interval = gmDateTime.pydt_now_here() - r['clin_when']),
1393 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes)
1394 )
1395 range_info = gmTools.coalesce (
1396 r.formatted_clinical_range,
1397 r.formatted_normal_range
1398 )
1399 tt = r.format(with_source_data = True)
1400 else:
1401 result_type = r
1402 result_val = _('missing')
1403 loinc_data = gmLOINC.loinc2data(r)
1404 if loinc_data is None:
1405 result_when = _('LOINC not found')
1406 tt = u''
1407 else:
1408 result_when = loinc_data['term']
1409 tt = gmLOINC.format_loinc(r)
1410 range_info = None
1411 items.append([result_type, result_val, result_when, gmTools.coalesce(range_info, '')])
1412 data.append({'data': r, 'formatted': tt})
1413
1414 self._LCTRL_results.set_string_items(items)
1415 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1416 self._LCTRL_results.set_data(data)
1417
1418 if len(items) > 0:
1419 self._LCTRL_results.Select(idx = 0, on = 1)
1420 self._LCTRL_results.SetFocus()
1421
1422 return True
1423
1424
1426 if panel is None:
1427 self._TCTRL_panel_comment.SetValue('')
1428 self._CHBOX_show_missing.Disable()
1429 else:
1430 pnl = self._PRW_panel.GetData(as_instance = True)
1431 self._TCTRL_panel_comment.SetValue(gmTools.coalesce(pnl['comment'], ''))
1432 self.__repopulate_ui()
1433 self._CHBOX_show_missing.Enable()
1434
1435
1437 self._TCTRL_panel_comment.SetValue('')
1438 if self._PRW_panel.Value.strip() == u'':
1439 self.__repopulate_ui()
1440 self._CHBOX_show_missing.Disable()
1441
1442
1443
1444
1446 if self.__patient is None:
1447 return True
1448
1449 if kwds['pk_identity'] is not None:
1450 if kwds['pk_identity'] != self.__patient.ID:
1451 return True
1452
1453 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results', 'clin.test_panel']:
1454 return True
1455
1456 self._schedule_data_reget()
1457 return True
1458
1459
1462
1463
1465 wx.CallAfter(self.__on_panel_selected, panel = panel)
1466
1467
1469 wx.CallAfter(self.__on_panel_selection_modified)
1470
1471
1480
1481
1489
1490
1492 event.Skip()
1493
1494 if self._PRW_panel.GetData(as_instance = False) is None:
1495 return
1496 self.__repopulate_ui()
1497
1498
1499
1500
1502 self.__repopulate_ui()
1503 return True
1504
1505
1506
1507
1509 return self.__patient
1510
1512 if (self.__patient is None) and (patient is None):
1513 return
1514 if (self.__patient is None) or (patient is None):
1515 self.__patient = patient
1516 self._schedule_data_reget()
1517 return
1518 if self.__patient.ID == patient.ID:
1519 return
1520 self.__patient = patient
1521 self._schedule_data_reget()
1522
1523 patient = property(_get_patient, _set_patient)
1524
1525
1526 from Gnumed.wxGladeWidgets import wxgMeasurementsAsTablePnl
1527
1528 -class cMeasurementsAsTablePnl(wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl, gmRegetMixin.cRegetOnPaintMixin):
1529 """A panel for holding a grid displaying all measurement results.
1530
1531 - operates on a cPatient instance handed to it and NOT on the currently active patient
1532 """
1542
1543
1544
1545
1547 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
1548
1549 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
1550 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
1551
1552 item = self.__action_button_popup.Append(-1, _('Plot'))
1553 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563 item = self.__action_button_popup.Append(-1, _('&Delete'))
1564 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
1565
1566
1567
1568
1569 self._GRID_results_all.show_by_panel = False
1570
1571
1574
1575
1577 self._GRID_results_all.patient = self.__patient
1578
1579 self.Layout()
1580 return True
1581
1582
1585
1586
1589
1590
1593
1594
1595
1596
1598 if self.__patient is None:
1599 return True
1600
1601 if kwds['pk_identity'] is not None:
1602 if kwds['pk_identity'] != self.__patient.ID:
1603 return True
1604
1605 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1606 return True
1607
1608 self._schedule_data_reget()
1609 return True
1610
1611
1614
1615
1619
1620
1623
1624
1630
1631
1632
1633
1635 self.__repopulate_ui()
1636 return True
1637
1638
1639
1640
1642 return self.__patient
1643
1645 if (self.__patient is None) and (patient is None):
1646 return
1647 if (self.__patient is None) or (patient is None):
1648 self.__patient = patient
1649 self._schedule_data_reget()
1650 return
1651 if self.__patient.ID == patient.ID:
1652 return
1653 self.__patient = patient
1654 self._schedule_data_reget()
1655
1656 patient = property(_get_patient, _set_patient)
1657
1658
1659
1660
1662 """Notebook displaying measurements pages:
1663
1664 - by test battery
1665 - by day
1666 - by issue/episode
1667 - most-recent list, perhaps by panel
1668 - full grid
1669 - full list
1670
1671 Used as a main notebook plugin page.
1672
1673 Operates on the active patient.
1674 """
1675
1690
1691
1692
1693
1695 for page_idx in range(self.GetPageCount()):
1696 page = self.GetPage(page_idx)
1697 page.patient = None
1698
1699
1700 - def _post_patient_selection(self, **kwds):
1701 for page_idx in range(self.GetPageCount()):
1702 page = self.GetPage(page_idx)
1703 page.patient = self.__patient.patient
1704
1705
1706
1707
1709 if self.__patient.connected:
1710 pat = self.__patient.patient
1711 else:
1712 pat = None
1713 for page_idx in range(self.GetPageCount()):
1714 page = self.GetPage(page_idx)
1715 page.patient = pat
1716
1717 return True
1718
1719
1720
1721
1723
1724
1725 new_page = cMeasurementsByDayPnl(self, -1)
1726 new_page.patient = None
1727 self.AddPage (
1728 page = new_page,
1729 text = _('Days'),
1730 select = True
1731 )
1732
1733
1734 new_page = cMeasurementsByIssuePnl(self, -1)
1735 new_page.patient = None
1736 self.AddPage (
1737 page = new_page,
1738 text = _('Problems'),
1739 select = False
1740 )
1741
1742
1743 new_page = cMeasurementsByBatteryPnl(self, -1)
1744 new_page.patient = None
1745 self.AddPage (
1746 page = new_page,
1747 text = _('Panels'),
1748 select = False
1749 )
1750
1751
1752 new_page = cMeasurementsAsMostRecentListPnl(self, -1)
1753 new_page.patient = None
1754 self.AddPage (
1755 page = new_page,
1756 text = _('Most recent'),
1757 select = False
1758 )
1759
1760
1761 new_page = cMeasurementsAsTablePnl(self, -1)
1762 new_page.patient = None
1763 self.AddPage (
1764 page = new_page,
1765 text = _('Table'),
1766 select = False
1767 )
1768
1769
1770 new_page = cMeasurementsAsListPnl(self, -1)
1771 new_page.patient = None
1772 self.AddPage (
1773 page = new_page,
1774 text = _('List'),
1775 select = False
1776 )
1777
1778
1779
1780
1782 return self.__patient
1783
1785 self.__patient = patient
1786 if self.__patient.connected:
1787 pat = self.__patient.patient
1788 else:
1789 pat = None
1790 for page_idx in range(self.GetPageCount()):
1791 page = self.GetPage(page_idx)
1792 page.patient = pat
1793
1794 patient = property(_get_patient, _set_patient)
1795
1796
1798 """A grid class for displaying measurement results.
1799
1800 - operates on a cPatient instance handed to it
1801 - does NOT listen to the currently active patient
1802 - thereby it can display any patient at any time
1803 """
1804
1805
1806
1807
1808
1810
1811 wx.grid.Grid.__init__(self, *args, **kwargs)
1812
1813 self.__patient = None
1814 self.__panel_to_show = None
1815 self.__show_by_panel = False
1816 self.__cell_data = {}
1817 self.__row_label_data = []
1818 self.__col_label_data = []
1819
1820 self.__prev_row = None
1821 self.__prev_col = None
1822 self.__prev_label_row = None
1823 self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::'))
1824
1825 self.__init_ui()
1826 self.__register_events()
1827
1828
1829
1830
1832 if not self.IsSelection():
1833 gmDispatcher.send(signal = 'statustext', msg = _('No results selected for deletion.'))
1834 return True
1835
1836 selected_cells = self.get_selected_cells()
1837 if len(selected_cells) > 20:
1838 results = None
1839 msg = _(
1840 'There are %s results marked for deletion.\n'
1841 '\n'
1842 'Are you sure you want to delete these results ?'
1843 ) % len(selected_cells)
1844 else:
1845 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1846 txt = '\n'.join([ '%s %s (%s): %s %s%s' % (
1847 r['clin_when'].strftime('%x %H:%M'),
1848 r['unified_abbrev'],
1849 r['unified_name'],
1850 r['unified_val'],
1851 r['val_unit'],
1852 gmTools.coalesce(r['abnormality_indicator'], '', ' (%s)')
1853 ) for r in results
1854 ])
1855 msg = _(
1856 'The following results are marked for deletion:\n'
1857 '\n'
1858 '%s\n'
1859 '\n'
1860 'Are you sure you want to delete these results ?'
1861 ) % txt
1862
1863 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
1864 self,
1865 -1,
1866 caption = _('Deleting test results'),
1867 question = msg,
1868 button_defs = [
1869 {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False},
1870 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True}
1871 ]
1872 )
1873 decision = dlg.ShowModal()
1874
1875 if decision == wx.ID_YES:
1876 if results is None:
1877 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1878 for result in results:
1879 gmPathLab.delete_test_result(result)
1880
1881
1883 if not self.IsSelection():
1884 gmDispatcher.send(signal = 'statustext', msg = _('Cannot sign results. No results selected.'))
1885 return True
1886
1887 selected_cells = self.get_selected_cells()
1888 tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1889
1890 return review_tests(parent = self, tests = tests)
1891
1892
1894
1895 if not self.IsSelection():
1896 gmDispatcher.send(signal = 'statustext', msg = _('Cannot plot results. No results selected.'))
1897 return True
1898
1899 tests = self.__cells_to_data (
1900 cells = self.get_selected_cells(),
1901 exclude_multi_cells = False,
1902 auto_include_multi_cells = True
1903 )
1904
1905 plot_measurements(parent = self, tests = tests)
1906
1908
1909 sel_block_top_left = self.GetSelectionBlockTopLeft()
1910 sel_block_bottom_right = self.GetSelectionBlockBottomRight()
1911 sel_cols = self.GetSelectedCols()
1912 sel_rows = self.GetSelectedRows()
1913
1914 selected_cells = []
1915
1916
1917 selected_cells += self.GetSelectedCells()
1918
1919
1920 selected_cells += list (
1921 (row, col)
1922 for row in sel_rows
1923 for col in range(self.GetNumberCols())
1924 )
1925
1926
1927 selected_cells += list (
1928 (row, col)
1929 for row in range(self.GetNumberRows())
1930 for col in sel_cols
1931 )
1932
1933
1934 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()):
1935 selected_cells += [
1936 (row, col)
1937 for row in range(top_left[0], bottom_right[0] + 1)
1938 for col in range(top_left[1], bottom_right[1] + 1)
1939 ]
1940
1941 return set(selected_cells)
1942
1943 - def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
1944 """Select a range of cells according to criteria.
1945
1946 unsigned_only: include only those which are not signed at all yet
1947 accountable_only: include only those for which the current user is responsible
1948 keep_preselections: broaden (rather than replace) the range of selected cells
1949
1950 Combinations are powerful !
1951 """
1952 wx.BeginBusyCursor()
1953 self.BeginBatch()
1954
1955 if not keep_preselections:
1956 self.ClearSelection()
1957
1958 for col_idx in self.__cell_data.keys():
1959 for row_idx in self.__cell_data[col_idx].keys():
1960
1961
1962 do_not_include = False
1963 for result in self.__cell_data[col_idx][row_idx]:
1964 if unsigned_only:
1965 if result['reviewed']:
1966 do_not_include = True
1967 break
1968 if accountables_only:
1969 if not result['you_are_responsible']:
1970 do_not_include = True
1971 break
1972 if do_not_include:
1973 continue
1974
1975 self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True)
1976
1977 self.EndBatch()
1978 wx.EndBusyCursor()
1979
1980
1982 self.empty_grid()
1983 if self.__patient is None:
1984 return
1985
1986 if self.__show_by_panel:
1987 if self.__panel_to_show is None:
1988 return
1989 tests = self.__panel_to_show.get_test_types_for_results (
1990 self.__patient.ID,
1991 order_by = 'unified_abbrev',
1992 unique_meta_types = True
1993 )
1994 self.__repopulate_grid (
1995 tests4rows = tests,
1996 test_pks2show = [ tt['pk_test_type'] for tt in self.__panel_to_show['test_types'] ]
1997 )
1998 return
1999
2000 emr = self.__patient.emr
2001 tests = emr.get_test_types_for_results(order_by = 'unified_abbrev', unique_meta_types = True)
2002 self.__repopulate_grid(tests4rows = tests)
2003
2004
2006
2007 if len(tests4rows) == 0:
2008 return
2009
2010 emr = self.__patient.emr
2011
2012 self.__row_label_data = tests4rows
2013 row_labels = [ '%s%s' % (
2014 gmTools.bool2subst(test_type['is_fake_meta_type'], '', gmTools.u_sum, ''),
2015 test_type['unified_abbrev']
2016 ) for test_type in self.__row_label_data
2017 ]
2018
2019 self.__col_label_data = [ d['clin_when_day'] for d in emr.get_dates_for_results (
2020 tests = test_pks2show,
2021 reverse_chronological = True
2022 )]
2023 col_labels = [ gmDateTime.pydt_strftime(date, self.__date_format, accuracy = gmDateTime.acc_days) for date in self.__col_label_data ]
2024
2025 results = emr.get_test_results_by_date (
2026 tests = test_pks2show,
2027 reverse_chronological = True
2028 )
2029
2030 self.BeginBatch()
2031
2032
2033 self.AppendRows(numRows = len(row_labels))
2034 for row_idx in range(len(row_labels)):
2035 self.SetRowLabelValue(row_idx, row_labels[row_idx])
2036
2037
2038 self.AppendCols(numCols = len(col_labels))
2039 for col_idx in range(len(col_labels)):
2040 self.SetColLabelValue(col_idx, col_labels[col_idx])
2041
2042
2043 for result in results:
2044 row_idx = row_labels.index('%s%s' % (
2045 gmTools.bool2subst(result['is_fake_meta_type'], '', gmTools.u_sum, ''),
2046 result['unified_abbrev']
2047 ))
2048 col_idx = col_labels.index(gmDateTime.pydt_strftime(result['clin_when'], self.__date_format, accuracy = gmDateTime.acc_days))
2049
2050 try:
2051 self.__cell_data[col_idx]
2052 except KeyError:
2053 self.__cell_data[col_idx] = {}
2054
2055
2056 if row_idx in self.__cell_data[col_idx]:
2057 self.__cell_data[col_idx][row_idx].append(result)
2058 self.__cell_data[col_idx][row_idx].sort(key = lambda x: x['clin_when'], reverse = True)
2059 else:
2060 self.__cell_data[col_idx][row_idx] = [result]
2061
2062
2063 vals2display = []
2064 cell_has_out_of_bounds_value = False
2065 for sub_result in self.__cell_data[col_idx][row_idx]:
2066
2067 if sub_result.is_considered_abnormal:
2068 cell_has_out_of_bounds_value = True
2069
2070 abnormality_indicator = sub_result.formatted_abnormality_indicator
2071 if abnormality_indicator is None:
2072 abnormality_indicator = ''
2073 if abnormality_indicator != '':
2074 abnormality_indicator = ' (%s)' % abnormality_indicator[:3]
2075
2076 missing_review = False
2077
2078
2079 if not sub_result['reviewed']:
2080 missing_review = True
2081
2082 else:
2083
2084 if sub_result['you_are_responsible'] and not sub_result['review_by_you']:
2085 missing_review = True
2086
2087 needs_superscript = False
2088
2089
2090 if sub_result.is_long_text:
2091 lines = gmTools.strip_empty_lines (
2092 text = sub_result['unified_val'],
2093 eol = '\n',
2094 return_list = True
2095 )
2096 needs_superscript = True
2097 tmp = lines[0][:7]
2098 else:
2099 val = gmTools.strip_empty_lines (
2100 text = sub_result['unified_val'],
2101 eol = '\n',
2102 return_list = False
2103 ).replace('\n', '//')
2104 if len(val) > 8:
2105 needs_superscript = True
2106 tmp = val[:7]
2107 else:
2108 tmp = '%.8s' % val[:8]
2109
2110
2111 tmp = '%s%.6s' % (tmp, abnormality_indicator)
2112
2113
2114 has_sub_result_comment = gmTools.coalesce (
2115 gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']),
2116 ''
2117 ).strip() != ''
2118 if has_sub_result_comment:
2119 needs_superscript = True
2120
2121 if needs_superscript:
2122 tmp = '%s%s' % (tmp, gmTools.u_superscript_one)
2123
2124
2125 if missing_review:
2126 tmp = '%s %s' % (tmp, gmTools.u_writing_hand)
2127 else:
2128 if sub_result['is_clinically_relevant']:
2129 tmp += ' !'
2130
2131
2132 if len(self.__cell_data[col_idx][row_idx]) > 1:
2133 tmp = '%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp)
2134
2135 vals2display.append(tmp)
2136
2137 self.SetCellValue(row_idx, col_idx, '\n'.join(vals2display))
2138 self.SetCellAlignment(row_idx, col_idx, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149 if cell_has_out_of_bounds_value:
2150
2151 self.SetCellBackgroundColour(row_idx, col_idx, 'PALE TURQUOISE')
2152
2153 self.EndBatch()
2154
2155 self.AutoSize()
2156 self.AdjustScrollbars()
2157 self.ForceRefresh()
2158
2159
2160
2161 return
2162
2163
2165 self.BeginBatch()
2166 self.ClearGrid()
2167
2168
2169 if self.GetNumberRows() > 0:
2170 self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
2171 if self.GetNumberCols() > 0:
2172 self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
2173 self.EndBatch()
2174 self.__cell_data = {}
2175 self.__row_label_data = []
2176 self.__col_label_data = []
2177
2178
2196
2197
2223
2224
2225
2226
2228
2229 self.SetMinSize((10, 10))
2230
2231 self.CreateGrid(0, 1)
2232 self.EnableEditing(0)
2233 self.EnableDragGridSize(1)
2234
2235
2236
2237
2238
2239
2240 self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE)
2241
2242 self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE)
2243 font = self.GetLabelFont()
2244 font.SetWeight(wx.FONTWEIGHT_LIGHT)
2245 self.SetLabelFont(font)
2246
2247
2248 dbcfg = gmCfg.cCfgSQL()
2249 url = dbcfg.get2 (
2250 option = 'external.urls.measurements_encyclopedia',
2251 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
2252 bias = 'user',
2253 default = gmPathLab.URL_test_result_information
2254 )
2255
2256 self.__WIN_corner = self.GetGridCornerLabelWindow()
2257
2258 LNK_lab = wxh.HyperlinkCtrl (
2259 self.__WIN_corner,
2260 -1,
2261 label = _('Tests'),
2262 style = wxh.HL_DEFAULT_STYLE
2263 )
2264 LNK_lab.SetURL(url)
2265 LNK_lab.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND))
2266 LNK_lab.SetToolTip(_(
2267 'Navigate to an encyclopedia of measurements\n'
2268 'and test methods on the web.\n'
2269 '\n'
2270 ' <%s>'
2271 ) % url)
2272
2273 SZR_inner = wx.BoxSizer(wx.HORIZONTAL)
2274 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)
2275 SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0)
2276 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)
2277
2278 SZR_corner = wx.BoxSizer(wx.VERTICAL)
2279 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)
2280 SZR_corner.Add(SZR_inner, 0, wx.EXPAND)
2281 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)
2282
2283 self.__WIN_corner.SetSizer(SZR_corner)
2284 SZR_corner.Fit(self.__WIN_corner)
2285
2286
2288 self.__WIN_corner.Layout()
2289
2290
2291 - def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
2292 """List of <cells> must be in row / col order."""
2293 data = []
2294 for row, col in cells:
2295 try:
2296
2297 data_list = self.__cell_data[col][row]
2298 except KeyError:
2299 continue
2300
2301 if len(data_list) == 1:
2302 data.append(data_list[0])
2303 continue
2304
2305 if exclude_multi_cells:
2306 gmDispatcher.send(signal = 'statustext', msg = _('Excluding multi-result field from further processing.'))
2307 continue
2308
2309 if auto_include_multi_cells:
2310 data.extend(data_list)
2311 continue
2312
2313 data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list)
2314 if data_to_include is None:
2315 continue
2316 data.extend(data_to_include)
2317
2318 return data
2319
2320
2322 data = gmListWidgets.get_choices_from_list (
2323 parent = self,
2324 msg = _(
2325 'Your selection includes a field with multiple results.\n'
2326 '\n'
2327 'Please select the individual results you want to work on:'
2328 ),
2329 caption = _('Selecting test results'),
2330 choices = [ [d['clin_when'], '%s: %s' % (d['abbrev_tt'], d['name_tt']), d['unified_val']] for d in cell_data ],
2331 columns = [ _('Date / Time'), _('Test'), _('Result') ],
2332 data = cell_data,
2333 single_selection = single_selection
2334 )
2335 return data
2336
2337
2338
2339
2341
2342 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
2343 self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels)
2344
2345
2346
2347 self.Bind(wx.EVT_SIZE, self.__resize_corner_window)
2348
2349
2350 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
2351
2352
2354 col = evt.GetCol()
2355 row = evt.GetRow()
2356
2357 try:
2358 self.__cell_data[col][row]
2359 except KeyError:
2360 fields = {}
2361 col_date = self.__col_label_data[col]
2362 fields['clin_when'] = {'data': col_date}
2363 test_type = self.__row_label_data[row]
2364 temporally_closest_result_of_row_type = test_type.meta_test_type.get_temporally_closest_result(col_date, self.__patient.ID)
2365 if temporally_closest_result_of_row_type is not None:
2366 fields['pk_test_type'] = {'data': temporally_closest_result_of_row_type['pk_test_type']}
2367 same_day_results = gmPathLab.get_results_for_day (
2368 timestamp = col_date,
2369 patient = self.__patient.ID,
2370 order_by = None
2371 )
2372 if len(same_day_results) > 0:
2373 fields['pk_episode'] = {'data': same_day_results[0]['pk_episode']}
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383 edit_measurement (
2384 parent = self,
2385 measurement = None,
2386 single_entry = True,
2387 fields = fields
2388 )
2389 return
2390
2391 if len(self.__cell_data[col][row]) > 1:
2392 data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True)
2393 else:
2394 data = self.__cell_data[col][row][0]
2395
2396 if data is None:
2397 return
2398
2399 edit_measurement(parent = self, measurement = data, single_entry = True)
2400
2401
2402
2403
2404
2405
2406
2407
2409
2410
2411
2412 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2413
2414 row = self.YToRow(y)
2415
2416 if self.__prev_label_row == row:
2417 return
2418
2419 self.__prev_label_row == row
2420
2421 evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row))
2422
2423
2424
2425
2426
2427
2428
2429
2431 """Calculate where the mouse is and set the tooltip dynamically."""
2432
2433
2434
2435 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449 row, col = self.XYToCell(x, y)
2450
2451 if (row == self.__prev_row) and (col == self.__prev_col):
2452 return
2453
2454 self.__prev_row = row
2455 self.__prev_col = col
2456
2457 evt.GetEventObject().SetToolTip(self.get_cell_tooltip(col=col, row=row))
2458
2459
2460
2461
2463 return self.__patient
2464
2468
2469 patient = property(_get_patient, _set_patient)
2470
2474
2475 panel_to_show = property(lambda x:x, _set_panel_to_show)
2476
2480
2481 show_by_panel = property(lambda x:x, _set_show_by_panel)
2482
2483
2484
2485
2486 from Gnumed.wxGladeWidgets import wxgMeasurementsPnl
2487
2488 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
2489 """Panel holding a grid with lab data. Used as notebook page."""
2490
2498
2499
2500
2502 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
2503 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
2504 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._schedule_data_reget)
2505 gmDispatcher.connect(signal = 'clin.reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
2506
2508 self._schedule_data_reget()
2509
2511 self._GRID_results_all.patient = None
2512 self._GRID_results_battery.patient = None
2513
2516
2520
2524
2527
2533
2536
2556
2559
2562
2565
2567 wx.CallAfter(self.__on_panel_selected, panel=panel)
2568
2570 if panel is None:
2571 self._TCTRL_panel_comment.SetValue('')
2572 self._GRID_results_battery.panel_to_show = None
2573
2574 self._PNL_results_battery_grid.Hide()
2575 else:
2576 pnl = self._PRW_panel.GetData(as_instance = True)
2577 self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
2578 pnl['comment'],
2579 ''
2580 ))
2581 self._GRID_results_battery.panel_to_show = pnl
2582
2583 self._PNL_results_battery_grid.Show()
2584 self._GRID_results_battery.Fit()
2585 self._GRID_results_all.Fit()
2586 self.Layout()
2587
2589 wx.CallAfter(self.__on_panel_selection_modified)
2590
2592 self._TCTRL_panel_comment.SetValue('')
2593 if self._PRW_panel.GetValue().strip() == '':
2594 self._GRID_results_battery.panel_to_show = None
2595
2596 self._PNL_results_battery_grid.Hide()
2597 self.Layout()
2598
2599
2600
2602 self.SetMinSize((10, 10))
2603
2604 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
2605
2606 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
2607 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
2608
2609 item = self.__action_button_popup.Append(-1, _('Plot'))
2610 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
2611
2612 item = self.__action_button_popup.Append(-1, _('Export to &file'))
2613 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item)
2614 self.__action_button_popup.Enable(id = menu_id, enable = False)
2615
2616 item = self.__action_button_popup.Append(-1, _('Export to &clipboard'))
2617 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item)
2618 self.__action_button_popup.Enable(id = menu_id, enable = False)
2619
2620 item = self.__action_button_popup.Append(-1, _('&Delete'))
2621 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
2622
2623
2624
2625
2626 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
2627 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
2628
2629 self._GRID_results_battery.show_by_panel = True
2630 self._GRID_results_battery.panel_to_show = None
2631
2632 self._PNL_results_battery_grid.Hide()
2633 self._BTN_display_mode.SetLabel(_('All: by &Day'))
2634
2635 self._PNL_results_all_grid.Show()
2636 self._PNL_results_all_listed.Hide()
2637 self.Layout()
2638
2639 self._PRW_panel.SetFocus()
2640
2641
2642
2644 pat = gmPerson.gmCurrentPatient()
2645 if pat.connected:
2646 self._GRID_results_battery.patient = pat
2647 if self.__display_mode == 'grid':
2648 self._GRID_results_all.patient = pat
2649 self._PNL_results_all_listed.patient = None
2650 else:
2651 self._GRID_results_all.patient = None
2652 self._PNL_results_all_listed.patient = pat
2653 else:
2654 self._GRID_results_battery.patient = None
2655 self._GRID_results_all.patient = None
2656 self._PNL_results_all_listed.patient = None
2657 return True
2658
2659
2660
2661
2663
2664 if tests is None:
2665 return True
2666
2667 if len(tests) == 0:
2668 return True
2669
2670 if parent is None:
2671 parent = wx.GetApp().GetTopWindow()
2672
2673 if len(tests) > 10:
2674 test_count = len(tests)
2675 tests2show = None
2676 else:
2677 test_count = None
2678 tests2show = tests
2679 if len(tests) == 0:
2680 return True
2681
2682 dlg = cMeasurementsReviewDlg(parent, -1, tests = tests, test_count = test_count)
2683 decision = dlg.ShowModal()
2684 if decision != wx.ID_APPLY:
2685 return True
2686
2687 wx.BeginBusyCursor()
2688 if dlg._RBTN_confirm_abnormal.GetValue():
2689 abnormal = None
2690 elif dlg._RBTN_results_normal.GetValue():
2691 abnormal = False
2692 else:
2693 abnormal = True
2694
2695 if dlg._RBTN_confirm_relevance.GetValue():
2696 relevant = None
2697 elif dlg._RBTN_results_not_relevant.GetValue():
2698 relevant = False
2699 else:
2700 relevant = True
2701
2702 comment = None
2703 if len(tests) == 1:
2704 comment = dlg._TCTRL_comment.GetValue()
2705
2706 make_responsible = dlg._CHBOX_responsible.IsChecked()
2707 dlg.Destroy()
2708
2709 for test in tests:
2710 test.set_review (
2711 technically_abnormal = abnormal,
2712 clinically_relevant = relevant,
2713 comment = comment,
2714 make_me_responsible = make_responsible
2715 )
2716 wx.EndBusyCursor()
2717
2718 return True
2719
2720
2721 from Gnumed.wxGladeWidgets import wxgMeasurementsReviewDlg
2722
2724
2726
2727 try:
2728 tests = kwargs['tests']
2729 del kwargs['tests']
2730 test_count = len(tests)
2731 try: del kwargs['test_count']
2732 except KeyError: pass
2733 except KeyError:
2734 tests = None
2735 test_count = kwargs['test_count']
2736 del kwargs['test_count']
2737
2738 wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs)
2739
2740 if tests is None:
2741 msg = _('%s results selected. Too many to list individually.') % test_count
2742 else:
2743 msg = '\n'.join (
2744 [ '%s: %s %s (%s)' % (
2745 t['unified_abbrev'],
2746 t['unified_val'],
2747 t['val_unit'],
2748 gmDateTime.pydt_strftime(t['clin_when'], '%Y %b %d')
2749 ) for t in tests
2750 ]
2751 )
2752
2753 self._LBL_tests.SetLabel(msg)
2754
2755 if test_count == 1:
2756 self._TCTRL_comment.Enable(True)
2757 self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], ''))
2758 if tests[0]['you_are_responsible']:
2759 self._CHBOX_responsible.Enable(False)
2760
2761 self.Fit()
2762
2763
2764
2770
2771
2772 from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl
2773
2774 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
2775 """This edit area saves *new* measurements into the active patient only."""
2776
2793
2794
2795
2796
2798 try:
2799 self._PRW_test.SetData(data = fields['pk_test_type']['data'])
2800 except KeyError:
2801 pass
2802 try:
2803 self._DPRW_evaluated.SetData(data = fields['clin_when']['data'])
2804 except KeyError:
2805 pass
2806 try:
2807 self._PRW_problem.SetData(data = fields['pk_episode']['data'])
2808 except KeyError:
2809 pass
2810 try:
2811 self._PRW_units.SetText(fields['val_unit']['data'], fields['val_unit']['data'], True)
2812 except KeyError:
2813 pass
2814 try:
2815 self._TCTRL_normal_min.SetValue(fields['val_normal_min']['data'])
2816 except KeyError:
2817 pass
2818 try:
2819 self._TCTRL_normal_max.SetValue(fields['val_normal_max']['data'])
2820 except KeyError:
2821 pass
2822 try:
2823 self._TCTRL_normal_range.SetValue(fields['val_normal_range']['data'])
2824 except KeyError:
2825 pass
2826 try:
2827 self._TCTRL_target_min.SetValue(fields['val_target_min']['data'])
2828 except KeyError:
2829 pass
2830 try:
2831 self._TCTRL_target_max.SetValue(fields['val_target_max']['data'])
2832 except KeyError:
2833 pass
2834 try:
2835 self._TCTRL_target_range.SetValue(fields['val_target_range']['data'])
2836 except KeyError:
2837 pass
2838
2839 self._TCTRL_result.SetFocus()
2840
2841
2873
2875 self._PRW_test.SetData(data = self.data['pk_test_type'])
2876 self.__refresh_loinc_info()
2877 self.__refresh_previous_value()
2878 self.__update_units_context()
2879 self._TCTRL_result.SetValue(self.data['unified_val'])
2880 self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True)
2881 self._PRW_abnormality_indicator.SetText (
2882 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2883 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2884 True
2885 )
2886 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2887 self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], ''))
2888 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2889 self._PRW_problem.SetData(self.data['pk_episode'])
2890 self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], ''))
2891 self._CHBOX_review.SetValue(False)
2892 self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False))
2893 self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False))
2894 self._CHBOX_abnormal.Enable(False)
2895 self._CHBOX_relevant.Enable(False)
2896 self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], ''))
2897 self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(self.data['val_normal_min'], '')))
2898 self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(self.data['val_normal_max'], '')))
2899 self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], ''))
2900 self._TCTRL_target_min.SetValue(str(gmTools.coalesce(self.data['val_target_min'], '')))
2901 self._TCTRL_target_max.SetValue(str(gmTools.coalesce(self.data['val_target_max'], '')))
2902 self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], ''))
2903 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], ''))
2904
2905 self._TCTRL_result.SetFocus()
2906
2908 self._PRW_test.SetText('', None, True)
2909 self.__refresh_loinc_info()
2910 self.__refresh_previous_value()
2911 self.__update_units_context()
2912 self._TCTRL_result.SetValue('')
2913 self._PRW_units.SetText('', None, True)
2914 self._PRW_abnormality_indicator.SetText('', None, True)
2915 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2916 self._TCTRL_note_test_org.SetValue('')
2917 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2918 self._PRW_problem.SetData(self.data['pk_episode'])
2919 self._TCTRL_narrative.SetValue('')
2920 self._CHBOX_review.SetValue(False)
2921 self._CHBOX_abnormal.SetValue(False)
2922 self._CHBOX_relevant.SetValue(False)
2923 self._CHBOX_abnormal.Enable(False)
2924 self._CHBOX_relevant.Enable(False)
2925 self._TCTRL_review_comment.SetValue('')
2926 self._TCTRL_normal_min.SetValue('')
2927 self._TCTRL_normal_max.SetValue('')
2928 self._TCTRL_normal_range.SetValue('')
2929 self._TCTRL_target_min.SetValue('')
2930 self._TCTRL_target_max.SetValue('')
2931 self._TCTRL_target_range.SetValue('')
2932 self._TCTRL_norm_ref_group.SetValue('')
2933
2934 self._PRW_test.SetFocus()
2935
2937
2938 validity = True
2939
2940 if not self._DPRW_evaluated.is_valid_timestamp():
2941 self._DPRW_evaluated.display_as_valid(False)
2942 validity = False
2943 else:
2944 self._DPRW_evaluated.display_as_valid(True)
2945
2946 val = self._TCTRL_result.GetValue().strip()
2947 if val == '':
2948 validity = False
2949 self.display_ctrl_as_valid(self._TCTRL_result, False)
2950 else:
2951 self.display_ctrl_as_valid(self._TCTRL_result, True)
2952 numeric, val = gmTools.input2decimal(val)
2953 if numeric:
2954 if self._PRW_units.GetValue().strip() == '':
2955 self._PRW_units.display_as_valid(False)
2956 validity = False
2957 else:
2958 self._PRW_units.display_as_valid(True)
2959 else:
2960 self._PRW_units.display_as_valid(True)
2961
2962 if self._PRW_problem.GetValue().strip() == '':
2963 self._PRW_problem.display_as_valid(False)
2964 validity = False
2965 else:
2966 self._PRW_problem.display_as_valid(True)
2967
2968 if self._PRW_test.GetValue().strip() == '':
2969 self._PRW_test.display_as_valid(False)
2970 validity = False
2971 else:
2972 self._PRW_test.display_as_valid(True)
2973
2974 if self._PRW_intended_reviewer.GetData() is None:
2975 self._PRW_intended_reviewer.display_as_valid(False)
2976 validity = False
2977 else:
2978 self._PRW_intended_reviewer.display_as_valid(True)
2979
2980 ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max]
2981 for widget in ctrls:
2982 val = widget.GetValue().strip()
2983 if val == '':
2984 continue
2985 try:
2986 decimal.Decimal(val.replace(',', '.', 1))
2987 self.display_ctrl_as_valid(widget, True)
2988 except:
2989 validity = False
2990 self.display_ctrl_as_valid(widget, False)
2991
2992 if validity is False:
2993 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save result. Invalid or missing essential input.'))
2994
2995 return validity
2996
2998
2999 emr = gmPerson.gmCurrentPatient().emr
3000
3001 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
3002 if success:
3003 v_num = result
3004 v_al = None
3005 else:
3006 v_al = self._TCTRL_result.GetValue().strip()
3007 v_num = None
3008
3009 pk_type = self._PRW_test.GetData()
3010 if pk_type is None:
3011 abbrev = self._PRW_test.GetValue().strip()
3012 name = self._PRW_test.GetValue().strip()
3013 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3014 lab = manage_measurement_orgs (
3015 parent = self,
3016 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
3017 )
3018 if lab is not None:
3019 lab = lab['pk_test_org']
3020 tt = gmPathLab.create_measurement_type (
3021 lab = lab,
3022 abbrev = abbrev,
3023 name = name,
3024 unit = unit
3025 )
3026 pk_type = tt['pk_test_type']
3027
3028 tr = emr.add_test_result (
3029 episode = self._PRW_problem.GetData(can_create=True, is_open=False),
3030 type = pk_type,
3031 intended_reviewer = self._PRW_intended_reviewer.GetData(),
3032 val_num = v_num,
3033 val_alpha = v_al,
3034 unit = self._PRW_units.GetValue()
3035 )
3036
3037 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
3038
3039 ctrls = [
3040 ('abnormality_indicator', self._PRW_abnormality_indicator),
3041 ('note_test_org', self._TCTRL_note_test_org),
3042 ('comment', self._TCTRL_narrative),
3043 ('val_normal_range', self._TCTRL_normal_range),
3044 ('val_target_range', self._TCTRL_target_range),
3045 ('norm_ref_group', self._TCTRL_norm_ref_group)
3046 ]
3047 for field, widget in ctrls:
3048 tr[field] = widget.GetValue().strip()
3049
3050 ctrls = [
3051 ('val_normal_min', self._TCTRL_normal_min),
3052 ('val_normal_max', self._TCTRL_normal_max),
3053 ('val_target_min', self._TCTRL_target_min),
3054 ('val_target_max', self._TCTRL_target_max)
3055 ]
3056 for field, widget in ctrls:
3057 val = widget.GetValue().strip()
3058 if val == '':
3059 tr[field] = None
3060 else:
3061 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
3062
3063 tr.save_payload()
3064
3065 if self._CHBOX_review.GetValue() is True:
3066 tr.set_review (
3067 technically_abnormal = self._CHBOX_abnormal.GetValue(),
3068 clinically_relevant = self._CHBOX_relevant.GetValue(),
3069 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
3070 make_me_responsible = False
3071 )
3072
3073 self.data = tr
3074
3075
3076
3077
3078
3079
3080
3081
3082 return True
3083
3085
3086 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
3087 if success:
3088 v_num = result
3089 v_al = None
3090 else:
3091 v_num = None
3092 v_al = self._TCTRL_result.GetValue().strip()
3093
3094 pk_type = self._PRW_test.GetData()
3095 if pk_type is None:
3096 abbrev = self._PRW_test.GetValue().strip()
3097 name = self._PRW_test.GetValue().strip()
3098 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3099 lab = manage_measurement_orgs (
3100 parent = self,
3101 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
3102 )
3103 if lab is not None:
3104 lab = lab['pk_test_org']
3105 tt = gmPathLab.create_measurement_type (
3106 lab = None,
3107 abbrev = abbrev,
3108 name = name,
3109 unit = unit
3110 )
3111 pk_type = tt['pk_test_type']
3112
3113 tr = self.data
3114
3115 tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False)
3116 tr['pk_test_type'] = pk_type
3117 tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData()
3118 tr['val_num'] = v_num
3119 tr['val_alpha'] = v_al
3120 tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3121 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
3122
3123 ctrls = [
3124 ('abnormality_indicator', self._PRW_abnormality_indicator),
3125 ('note_test_org', self._TCTRL_note_test_org),
3126 ('comment', self._TCTRL_narrative),
3127 ('val_normal_range', self._TCTRL_normal_range),
3128 ('val_target_range', self._TCTRL_target_range),
3129 ('norm_ref_group', self._TCTRL_norm_ref_group)
3130 ]
3131 for field, widget in ctrls:
3132 tr[field] = widget.GetValue().strip()
3133
3134 ctrls = [
3135 ('val_normal_min', self._TCTRL_normal_min),
3136 ('val_normal_max', self._TCTRL_normal_max),
3137 ('val_target_min', self._TCTRL_target_min),
3138 ('val_target_max', self._TCTRL_target_max)
3139 ]
3140 for field, widget in ctrls:
3141 val = widget.GetValue().strip()
3142 if val == '':
3143 tr[field] = None
3144 else:
3145 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
3146
3147 tr.save_payload()
3148
3149 if self._CHBOX_review.GetValue() is True:
3150 tr.set_review (
3151 technically_abnormal = self._CHBOX_abnormal.GetValue(),
3152 clinically_relevant = self._CHBOX_relevant.GetValue(),
3153 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
3154 make_me_responsible = False
3155 )
3156
3157
3158
3159
3160
3161
3162
3163
3164 return True
3165
3166
3167
3172
3174 self.__refresh_loinc_info()
3175 self.__refresh_previous_value()
3176 self.__update_units_context()
3177
3178 self.__update_normal_range()
3179 self.__update_clinical_range()
3180
3182
3183 self.__update_normal_range()
3184 self.__update_clinical_range()
3185
3187
3188 if not self._CHBOX_review.GetValue():
3189 self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != '')
3190
3195
3211
3215
3216
3217
3219
3220 if self._PRW_test.GetData() is None:
3221 self._PRW_units.unset_context(context = 'pk_type')
3222 self._PRW_units.unset_context(context = 'loinc')
3223 if self._PRW_test.GetValue().strip() == '':
3224 self._PRW_units.unset_context(context = 'test_name')
3225 else:
3226 self._PRW_units.set_context(context = 'test_name', val = self._PRW_test.GetValue().strip())
3227 return
3228
3229 tt = self._PRW_test.GetData(as_instance = True)
3230
3231 self._PRW_units.set_context(context = 'pk_type', val = tt['pk_test_type'])
3232 self._PRW_units.set_context(context = 'test_name', val = tt['name'])
3233
3234 if tt['loinc'] is not None:
3235 self._PRW_units.set_context(context = 'loinc', val = tt['loinc'])
3236
3237
3238 if self._PRW_units.GetValue().strip() == '':
3239 clin_when = self._DPRW_evaluated.GetData()
3240 if clin_when is None:
3241 unit = tt.temporally_closest_unit
3242 else:
3243 clin_when = clin_when.get_pydt()
3244 unit = tt.get_temporally_closest_unit(timestamp = clin_when)
3245 if unit is None:
3246 self._PRW_units.SetText('', unit, True)
3247 else:
3248 self._PRW_units.SetText(unit, unit, True)
3249
3250
3271
3272
3293
3294
3296
3297 self._TCTRL_loinc.SetValue('')
3298
3299 if self._PRW_test.GetData() is None:
3300 return
3301
3302 tt = self._PRW_test.GetData(as_instance = True)
3303
3304 if tt['loinc'] is None:
3305 return
3306
3307 info = gmLOINC.loinc2term(loinc = tt['loinc'])
3308 if len(info) == 0:
3309 self._TCTRL_loinc.SetValue('')
3310 return
3311
3312 self._TCTRL_loinc.SetValue('%s: %s' % (tt['loinc'], info[0]))
3313
3315 self._TCTRL_previous_value.SetValue('')
3316
3317
3318 if self.data is not None:
3319 return
3320 if self._PRW_test.GetData() is None:
3321 return
3322 tt = self._PRW_test.GetData(as_instance = True)
3323 most_recent = tt.get_most_recent_results (
3324 no_of_results = 1,
3325 patient = gmPerson.gmCurrentPatient().ID
3326 )
3327 if most_recent is None:
3328 return
3329 self._TCTRL_previous_value.SetValue(_('%s ago: %s%s%s - %s%s') % (
3330 gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - most_recent['clin_when']),
3331 most_recent['unified_val'],
3332 most_recent['val_unit'],
3333 gmTools.coalesce(most_recent['abnormality_indicator'], '', ' (%s)'),
3334 most_recent['abbrev_tt'],
3335 gmTools.coalesce(most_recent.formatted_range, '', ' [%s]')
3336 ))
3337 self._TCTRL_previous_value.SetToolTip(most_recent.format (
3338 with_review = True,
3339 with_evaluation = False,
3340 with_ranges = True,
3341 with_episode = True,
3342 with_type_details=True
3343 ))
3344
3345
3346
3347
3349
3350 if parent is None:
3351 parent = wx.GetApp().GetTopWindow()
3352
3353 if msg is None:
3354 msg = _('Pick the relevant measurement types.')
3355
3356 if right_column is None:
3357 right_columns = [_('Picked')]
3358 else:
3359 right_columns = [right_column]
3360
3361 picker = gmListWidgets.cItemPickerDlg(parent, -1, msg = msg)
3362 picker.set_columns(columns = [_('Known measurement types')], columns_right = right_columns)
3363 types = gmPathLab.get_measurement_types(order_by = 'unified_abbrev')
3364 picker.set_choices (
3365 choices = [
3366 '%s: %s%s' % (
3367 t['unified_abbrev'],
3368 t['unified_name'],
3369 gmTools.coalesce(t['name_org'], '', ' (%s)')
3370 )
3371 for t in types
3372 ],
3373 data = types
3374 )
3375 if picks is not None:
3376 picker.set_picks (
3377 picks = [
3378 '%s: %s%s' % (
3379 p['unified_abbrev'],
3380 p['unified_name'],
3381 gmTools.coalesce(p['name_org'], '', ' (%s)')
3382 )
3383 for p in picks
3384 ],
3385 data = picks
3386 )
3387 result = picker.ShowModal()
3388
3389 if result == wx.ID_CANCEL:
3390 picker.Destroy()
3391 return None
3392
3393 picks = picker.picks
3394 picker.Destroy()
3395 return picks
3396
3397
3420
3421
3422 def delete(measurement_type):
3423 if measurement_type.in_use:
3424 gmDispatcher.send (
3425 signal = 'statustext',
3426 beep = True,
3427 msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev'])
3428 )
3429 return False
3430 gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type'])
3431 return True
3432
3433
3434 def get_tooltip(test_type):
3435 return test_type.format()
3436
3437
3438 def manage_aggregates(test_type):
3439 manage_meta_test_types(parent = parent)
3440 return False
3441
3442
3443 def manage_panels_of_type(test_type):
3444 if test_type['loinc'] is None:
3445 return False
3446 all_panels = gmPathLab.get_test_panels(order_by = 'description')
3447 curr_panels = test_type.test_panels
3448 if curr_panels is None:
3449 curr_panels = []
3450 panel_candidates = [ p for p in all_panels if p['pk_test_panel'] not in [
3451 c_pnl['pk_test_panel'] for c_pnl in curr_panels
3452 ] ]
3453 picker = gmListWidgets.cItemPickerDlg(parent, -1, title = 'Panels with [%s]' % test_type['abbrev'])
3454 picker.set_columns(['Panels available'], ['Panels [%s] is to be on' % test_type['abbrev']])
3455 picker.set_choices (
3456 choices = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in panel_candidates ],
3457 data = panel_candidates
3458 )
3459 picker.set_picks (
3460 picks = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in curr_panels ],
3461 data = curr_panels
3462 )
3463 exit_type = picker.ShowModal()
3464 if exit_type == wx.ID_CANCEL:
3465 return False
3466
3467
3468 panels2add = [ p for p in picker.picks if p['pk_test_panel'] not in [
3469 c_pnl['pk_test_panel'] for c_pnl in curr_panels
3470 ] ]
3471
3472 panels2remove = [ p for p in curr_panels if p['pk_test_panel'] not in [
3473 picked_pnl['pk_test_panel'] for picked_pnl in picker.picks
3474 ] ]
3475 for new_panel in panels2add:
3476 new_panel.add_loinc(test_type['loinc'])
3477 for stale_panel in panels2remove:
3478 stale_panel.remove_loinc(test_type['loinc'])
3479
3480 return True
3481
3482
3483 def refresh(lctrl):
3484 mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev')
3485 items = [ [
3486 m['abbrev'],
3487 m['name'],
3488 gmTools.coalesce(m['reference_unit'], ''),
3489 gmTools.coalesce(m['loinc'], ''),
3490 gmTools.coalesce(m['comment_type'], ''),
3491 gmTools.coalesce(m['name_org'], '?'),
3492 gmTools.coalesce(m['comment_org'], ''),
3493 m['pk_test_type']
3494 ] for m in mtypes ]
3495 lctrl.set_string_items(items)
3496 lctrl.set_data(mtypes)
3497
3498
3499 gmListWidgets.get_choices_from_list (
3500 parent = parent,
3501 caption = _('Measurement types.'),
3502 columns = [ _('Abbrev'), _('Name'), _('Unit'), _('LOINC'), _('Comment'), _('Org'), _('Comment'), '#' ],
3503 single_selection = True,
3504 refresh_callback = refresh,
3505 edit_callback = edit,
3506 new_callback = edit,
3507 delete_callback = delete,
3508 list_tooltip_callback = get_tooltip,
3509 left_extra_button = (_('%s &Aggregate') % gmTools.u_sum, _('Manage aggregations (%s) of tests into groups.') % gmTools.u_sum, manage_aggregates),
3510 middle_extra_button = (_('Select panels'), _('Select panels the focussed test type is to belong to.'), manage_panels_of_type)
3511 )
3512
3513
3515
3517
3518 query = """
3519 SELECT DISTINCT ON (field_label)
3520 pk_test_type AS data,
3521 name
3522 || ' ('
3523 || coalesce (
3524 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
3525 '%(in_house)s'
3526 )
3527 || ')'
3528 AS field_label,
3529 name
3530 || ' ('
3531 || abbrev || ', '
3532 || coalesce(abbrev_meta || ': ' || name_meta || ', ', '')
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 list_label
3539 FROM
3540 clin.v_test_types c_vtt
3541 WHERE
3542 abbrev_meta %%(fragment_condition)s
3543 OR
3544 name_meta %%(fragment_condition)s
3545 OR
3546 abbrev %%(fragment_condition)s
3547 OR
3548 name %%(fragment_condition)s
3549 ORDER BY field_label
3550 LIMIT 50""" % {'in_house': _('generic / in house lab')}
3551
3552 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3553 mp.setThresholds(1, 2, 4)
3554 mp.word_separators = '[ \t:@]+'
3555 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
3556 self.matcher = mp
3557 self.SetToolTip(_('Select the type of measurement.'))
3558 self.selection_only = False
3559
3560
3566
3567
3568 from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl
3569
3570 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
3571
3588
3589
3591
3592
3593 query = """
3594 select distinct on (name)
3595 pk,
3596 name
3597 from clin.test_type
3598 where
3599 name %(fragment_condition)s
3600 order by name
3601 limit 50"""
3602 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3603 mp.setThresholds(1, 2, 4)
3604 self._PRW_name.matcher = mp
3605 self._PRW_name.selection_only = False
3606 self._PRW_name.add_callback_on_lose_focus(callback = self._on_name_lost_focus)
3607
3608
3609 query = """
3610 select distinct on (abbrev)
3611 pk,
3612 abbrev
3613 from clin.test_type
3614 where
3615 abbrev %(fragment_condition)s
3616 order by abbrev
3617 limit 50"""
3618 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3619 mp.setThresholds(1, 2, 3)
3620 self._PRW_abbrev.matcher = mp
3621 self._PRW_abbrev.selection_only = False
3622
3623
3624 self._PRW_reference_unit.selection_only = False
3625
3626
3627 mp = gmLOINC.cLOINCMatchProvider()
3628 mp.setThresholds(1, 2, 4)
3629
3630
3631 self._PRW_loinc.matcher = mp
3632 self._PRW_loinc.selection_only = False
3633 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
3634
3635
3637
3638 test = self._PRW_name.GetValue().strip()
3639
3640 if test == '':
3641 self._PRW_reference_unit.unset_context(context = 'test_name')
3642 return
3643
3644 self._PRW_reference_unit.set_context(context = 'test_name', val = test)
3645
3646
3648 loinc = self._PRW_loinc.GetData()
3649
3650 if loinc is None:
3651 self._TCTRL_loinc_info.SetValue('')
3652 self._PRW_reference_unit.unset_context(context = 'loinc')
3653 return
3654
3655 self._PRW_reference_unit.set_context(context = 'loinc', val = loinc)
3656
3657 info = gmLOINC.loinc2term(loinc = loinc)
3658 if len(info) == 0:
3659 self._TCTRL_loinc_info.SetValue('')
3660 return
3661
3662 self._TCTRL_loinc_info.SetValue(info[0])
3663
3664
3665
3666
3668
3669 has_errors = False
3670 for field in [self._PRW_name, self._PRW_abbrev, self._PRW_reference_unit]:
3671 if field.GetValue().strip() in ['', None]:
3672 has_errors = True
3673 field.display_as_valid(valid = False)
3674 else:
3675 field.display_as_valid(valid = True)
3676 field.Refresh()
3677
3678 return (not has_errors)
3679
3680
3710
3749
3750
3752 self._PRW_name.SetText('', None, True)
3753 self._on_name_lost_focus()
3754 self._PRW_abbrev.SetText('', None, True)
3755 self._PRW_reference_unit.SetText('', None, True)
3756 self._PRW_loinc.SetText('', None, True)
3757 self._on_loinc_lost_focus()
3758 self._TCTRL_comment_type.SetValue('')
3759 self._PRW_test_org.SetText('', None, True)
3760 self._PRW_meta_type.SetText('', None, True)
3761
3762 self._PRW_name.SetFocus()
3763
3765 self._PRW_name.SetText(self.data['name'], self.data['name'], True)
3766 self._on_name_lost_focus()
3767 self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True)
3768 self._PRW_reference_unit.SetText (
3769 gmTools.coalesce(self.data['reference_unit'], ''),
3770 self.data['reference_unit'],
3771 True
3772 )
3773 self._PRW_loinc.SetText (
3774 gmTools.coalesce(self.data['loinc'], ''),
3775 self.data['loinc'],
3776 True
3777 )
3778 self._on_loinc_lost_focus()
3779 self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], ''))
3780 self._PRW_test_org.SetText (
3781 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
3782 self.data['pk_test_org'],
3783 True
3784 )
3785 if self.data['pk_meta_test_type'] is None:
3786 self._PRW_meta_type.SetText('', None, True)
3787 else:
3788 self._PRW_meta_type.SetText('%s: %s' % (self.data['abbrev_meta'], self.data['name_meta']), self.data['pk_meta_test_type'], True)
3789
3790 self._PRW_name.SetFocus()
3791
3793 self._refresh_as_new()
3794 self._PRW_test_org.SetText (
3795 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
3796 self.data['pk_test_org'],
3797 True
3798 )
3799 self._PRW_name.SetFocus()
3800
3801
3802 _SQL_units_from_test_results = """
3803 -- via clin.v_test_results.pk_type (for types already used in results)
3804 SELECT
3805 val_unit AS data,
3806 val_unit AS field_label,
3807 val_unit || ' (' || name_tt || ')' AS list_label,
3808 1 AS rank
3809 FROM
3810 clin.v_test_results
3811 WHERE
3812 (
3813 val_unit %(fragment_condition)s
3814 OR
3815 reference_unit %(fragment_condition)s
3816 )
3817 %(ctxt_type_pk)s
3818 %(ctxt_test_name)s
3819 """
3820
3821 _SQL_units_from_test_types = """
3822 -- via clin.test_type (for types not yet used in results)
3823 SELECT
3824 reference_unit AS data,
3825 reference_unit AS field_label,
3826 reference_unit || ' (' || name || ')' AS list_label,
3827 2 AS rank
3828 FROM
3829 clin.test_type
3830 WHERE
3831 reference_unit %(fragment_condition)s
3832 %(ctxt_ctt)s
3833 """
3834
3835 _SQL_units_from_loinc_ipcc = """
3836 -- via ref.loinc.ipcc_units
3837 SELECT
3838 ipcc_units AS data,
3839 ipcc_units AS field_label,
3840 ipcc_units || ' (LOINC.ipcc: ' || term || ')' AS list_label,
3841 3 AS rank
3842 FROM
3843 ref.loinc
3844 WHERE
3845 ipcc_units %(fragment_condition)s
3846 %(ctxt_loinc)s
3847 %(ctxt_loinc_term)s
3848 """
3849
3850 _SQL_units_from_loinc_submitted = """
3851 -- via ref.loinc.submitted_units
3852 SELECT
3853 submitted_units AS data,
3854 submitted_units AS field_label,
3855 submitted_units || ' (LOINC.submitted:' || term || ')' AS list_label,
3856 3 AS rank
3857 FROM
3858 ref.loinc
3859 WHERE
3860 submitted_units %(fragment_condition)s
3861 %(ctxt_loinc)s
3862 %(ctxt_loinc_term)s
3863 """
3864
3865 _SQL_units_from_loinc_example = """
3866 -- via ref.loinc.example_units
3867 SELECT
3868 example_units AS data,
3869 example_units AS field_label,
3870 example_units || ' (LOINC.example: ' || term || ')' AS list_label,
3871 3 AS rank
3872 FROM
3873 ref.loinc
3874 WHERE
3875 example_units %(fragment_condition)s
3876 %(ctxt_loinc)s
3877 %(ctxt_loinc_term)s
3878 """
3879
3880 _SQL_units_from_substance_doses = """
3881 -- via ref.v_substance_doses.unit
3882 SELECT
3883 unit AS data,
3884 unit AS field_label,
3885 unit || ' (' || substance || ')' AS list_label,
3886 2 AS rank
3887 FROM
3888 ref.v_substance_doses
3889 WHERE
3890 unit %(fragment_condition)s
3891 %(ctxt_substance)s
3892 """
3893
3894 _SQL_units_from_substance_doses2 = """
3895 -- via ref.v_substance_doses.dose_unit
3896 SELECT
3897 dose_unit AS data,
3898 dose_unit AS field_label,
3899 dose_unit || ' (' || substance || ')' AS list_label,
3900 2 AS rank
3901 FROM
3902 ref.v_substance_doses
3903 WHERE
3904 dose_unit %(fragment_condition)s
3905 %(ctxt_substance)s
3906 """
3907
3908
3910
3912
3913 query = """
3914 SELECT DISTINCT ON (data)
3915 data,
3916 field_label,
3917 list_label
3918 FROM (
3919
3920 SELECT
3921 data,
3922 field_label,
3923 list_label,
3924 rank
3925 FROM (
3926 (%s) UNION ALL
3927 (%s) UNION ALL
3928 (%s) UNION ALL
3929 (%s) UNION ALL
3930 (%s) UNION ALL
3931 (%s) UNION ALL
3932 (%s)
3933 ) AS all_matching_units
3934 WHERE data IS NOT NULL
3935 ORDER BY rank, list_label
3936
3937 ) AS ranked_matching_units
3938 LIMIT 50""" % (
3939 _SQL_units_from_test_results,
3940 _SQL_units_from_test_types,
3941 _SQL_units_from_loinc_ipcc,
3942 _SQL_units_from_loinc_submitted,
3943 _SQL_units_from_loinc_example,
3944 _SQL_units_from_substance_doses,
3945 _SQL_units_from_substance_doses2
3946 )
3947
3948 ctxt = {
3949 'ctxt_type_pk': {
3950 'where_part': 'AND pk_test_type = %(pk_type)s',
3951 'placeholder': 'pk_type'
3952 },
3953 'ctxt_test_name': {
3954 'where_part': 'AND %(test_name)s IN (name_tt, name_meta, abbrev_meta)',
3955 'placeholder': 'test_name'
3956 },
3957 'ctxt_ctt': {
3958 'where_part': 'AND %(test_name)s IN (name, abbrev)',
3959 'placeholder': 'test_name'
3960 },
3961 'ctxt_loinc': {
3962 'where_part': 'AND code = %(loinc)s',
3963 'placeholder': 'loinc'
3964 },
3965 'ctxt_loinc_term': {
3966 'where_part': 'AND term ~* %(test_name)s',
3967 'placeholder': 'test_name'
3968 },
3969 'ctxt_substance': {
3970 'where_part': 'AND description ~* %(substance)s',
3971 'placeholder': 'substance'
3972 }
3973 }
3974
3975 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = ctxt)
3976 mp.setThresholds(1, 2, 4)
3977
3978 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
3979 self.matcher = mp
3980 self.SetToolTip(_('Select the desired unit for the amount or measurement.'))
3981 self.selection_only = False
3982 self.phrase_separators = '[;|]+'
3983
3984
3985
3986
3988
3990
3991 query = """
3992 select distinct abnormality_indicator,
3993 abnormality_indicator, abnormality_indicator
3994 from clin.v_test_results
3995 where
3996 abnormality_indicator %(fragment_condition)s
3997 order by abnormality_indicator
3998 limit 25"""
3999
4000 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4001 mp.setThresholds(1, 1, 2)
4002 mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"'
4003 mp.word_separators = '[ \t&:]+'
4004 gmPhraseWheel.cPhraseWheel.__init__ (
4005 self,
4006 *args,
4007 **kwargs
4008 )
4009 self.matcher = mp
4010 self.SetToolTip(_('Select an indicator for the level of abnormality.'))
4011 self.selection_only = False
4012
4013
4014
4015
4027
4036
4037 def refresh(lctrl):
4038 orgs = gmPathLab.get_test_orgs()
4039 lctrl.set_string_items ([
4040 (o['unit'], o['organization'], gmTools.coalesce(o['test_org_contact'], ''), gmTools.coalesce(o['comment'], ''), o['pk_test_org'])
4041 for o in orgs
4042 ])
4043 lctrl.set_data(orgs)
4044
4045 def delete(test_org):
4046 gmPathLab.delete_test_org(test_org = test_org['pk_test_org'])
4047 return True
4048
4049 if msg is None:
4050 msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n')
4051
4052 return gmListWidgets.get_choices_from_list (
4053 parent = parent,
4054 msg = msg,
4055 caption = _('Showing diagnostic orgs.'),
4056 columns = [_('Name'), _('Organization'), _('Contact'), _('Comment'), '#'],
4057 single_selection = True,
4058 refresh_callback = refresh,
4059 edit_callback = edit,
4060 new_callback = edit,
4061 delete_callback = delete
4062 )
4063
4064
4065 from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl
4066
4067 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
4068
4084
4085
4086
4087
4088
4089
4090
4091
4093 has_errors = False
4094 if self._PRW_org_unit.GetData() is None:
4095 if self._PRW_org_unit.GetValue().strip() == '':
4096 has_errors = True
4097 self._PRW_org_unit.display_as_valid(valid = False)
4098 else:
4099 self._PRW_org_unit.display_as_valid(valid = True)
4100 else:
4101 self._PRW_org_unit.display_as_valid(valid = True)
4102
4103 return (not has_errors)
4104
4115
4135
4140
4145
4147 self._refresh_as_new()
4148
4151
4152
4154
4156
4157 query = """
4158 SELECT DISTINCT ON (list_label)
4159 pk_test_org AS data,
4160 unit || ' (' || organization || ')' AS field_label,
4161 unit || ' @ ' || organization AS list_label
4162 FROM clin.v_test_orgs
4163 WHERE
4164 unit %(fragment_condition)s
4165 OR
4166 organization %(fragment_condition)s
4167 ORDER BY list_label
4168 LIMIT 50"""
4169 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4170 mp.setThresholds(1, 2, 4)
4171
4172 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4173 self.matcher = mp
4174 self.SetToolTip(_('The name of the path lab/diagnostic organisation.'))
4175 self.selection_only = False
4176
4189
4192
4193
4194
4195
4212
4213
4222
4223 def delete(meta_test_type):
4224 gmPathLab.delete_meta_type(meta_type = meta_test_type['pk'])
4225 return True
4226
4227 def get_tooltip(data):
4228 if data is None:
4229 return None
4230 return data.format(with_tests = True)
4231
4232 def refresh(lctrl):
4233 mtts = gmPathLab.get_meta_test_types()
4234 items = [ [
4235 m['abbrev'],
4236 m['name'],
4237 gmTools.coalesce(m['loinc'], ''),
4238 gmTools.coalesce(m['comment'], ''),
4239 m['pk']
4240 ] for m in mtts ]
4241 lctrl.set_string_items(items)
4242 lctrl.set_data(mtts)
4243
4244
4245 msg = _(
4246 '\n'
4247 'These are the meta test types currently defined in GNUmed.\n'
4248 '\n'
4249 'Meta test types allow you to aggregate several actual test types used\n'
4250 'by pathology labs into one logical type.\n'
4251 '\n'
4252 'This is useful for grouping together results of tests which come under\n'
4253 'different names but really are the same thing. This often happens when\n'
4254 'you switch labs or the lab starts using another test method.\n'
4255 )
4256
4257 gmListWidgets.get_choices_from_list (
4258 parent = parent,
4259 msg = msg,
4260 caption = _('Showing meta test types.'),
4261 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), '#'],
4262 single_selection = True,
4263 list_tooltip_callback = get_tooltip,
4264 edit_callback = edit,
4265 new_callback = edit,
4266 delete_callback = delete,
4267 refresh_callback = refresh
4268 )
4269
4270
4315
4316
4317 from Gnumed.wxGladeWidgets import wxgMetaTestTypeEAPnl
4318
4465
4466
4467
4468
4470 ea = cTestPanelEAPnl(parent, -1)
4471 ea.data = test_panel
4472 ea.mode = gmTools.coalesce(test_panel, 'new', 'edit')
4473 dlg = gmEditArea.cGenericEditAreaDlg2 (
4474 parent = parent,
4475 id = -1,
4476 edit_area = ea,
4477 single_entry = gmTools.bool2subst((test_panel is None), False, True)
4478 )
4479 dlg.SetTitle(gmTools.coalesce(test_panel, _('Adding new test panel'), _('Editing test panel')))
4480 if dlg.ShowModal() == wx.ID_OK:
4481 dlg.Destroy()
4482 return True
4483 dlg.Destroy()
4484 return False
4485
4486
4495
4496 def delete(test_panel):
4497 gmPathLab.delete_test_panel(pk = test_panel['pk_test_panel'])
4498 return True
4499
4500 def get_tooltip(test_panel):
4501 return test_panel.format()
4502
4503 def refresh(lctrl):
4504 panels = gmPathLab.get_test_panels(order_by = 'description')
4505 items = [ [
4506 p['description'],
4507 gmTools.coalesce(p['comment'], ''),
4508 p['pk_test_panel']
4509 ] for p in panels ]
4510 lctrl.set_string_items(items)
4511 lctrl.set_data(panels)
4512
4513 gmListWidgets.get_choices_from_list (
4514 parent = parent,
4515 caption = 'GNUmed: ' + _('Test panels list'),
4516 columns = [ _('Name'), _('Comment'), '#' ],
4517 single_selection = True,
4518 refresh_callback = refresh,
4519 edit_callback = edit,
4520 new_callback = edit,
4521 delete_callback = delete,
4522 list_tooltip_callback = get_tooltip
4523 )
4524
4525
4527
4529 query = """
4530 SELECT
4531 pk_test_panel
4532 AS data,
4533 description
4534 AS field_label,
4535 description
4536 AS list_label
4537 FROM
4538 clin.v_test_panels
4539 WHERE
4540 description %(fragment_condition)s
4541 ORDER BY field_label
4542 LIMIT 30"""
4543 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4544 mp.setThresholds(1, 2, 4)
4545
4546 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4547 self.matcher = mp
4548 self.SetToolTip(_('Select a test panel.'))
4549 self.selection_only = True
4550
4555
4560
4561
4562 from Gnumed.wxGladeWidgets import wxgTestPanelEAPnl
4563
4564 -class cTestPanelEAPnl(wxgTestPanelEAPnl.wxgTestPanelEAPnl, gmEditArea.cGenericEditAreaMixin):
4565
4585
4586
4588 self._LCTRL_loincs.set_columns([_('LOINC'), _('Term'), _('Units')])
4589 self._LCTRL_loincs.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
4590
4591 self._LCTRL_loincs.delete_callback = self._remove_loincs_from_list
4592 self.__refresh_loinc_list()
4593
4594 self._PRW_loinc.final_regex = r'.*'
4595 self._PRW_loinc.add_callback_on_selection(callback = self._on_loinc_selected)
4596
4597
4599 self._LCTRL_loincs.remove_items_safely()
4600 if self.__loincs is None:
4601 if self.data is None:
4602 return
4603 self.__loincs = self.data['loincs']
4604
4605 items = []
4606 for loinc in self.__loincs:
4607 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4608 if loinc_detail is None:
4609
4610 ttypes = gmPathLab.get_measurement_types(loincs = [loinc])
4611 if len(ttypes) == 0:
4612 items.append([loinc, _('LOINC not found'), ''])
4613 else:
4614 for tt in ttypes:
4615 items.append([loinc, _('not a LOINC') + u'; %(name)s @ %(name_org)s [#%(pk_test_type)s]' % tt, ''])
4616 continue
4617 items.append ([
4618 loinc,
4619 loinc_detail['term'],
4620 gmTools.coalesce(loinc_detail['example_units'], '', '%s')
4621 ])
4622
4623 self._LCTRL_loincs.set_string_items(items)
4624 self._LCTRL_loincs.set_column_widths()
4625
4626
4627
4628
4630 validity = True
4631
4632 if self.__loincs is None:
4633 if self.data is not None:
4634 self.__loincs = self.data['loincs']
4635
4636 if self.__loincs is None:
4637
4638 self.status_message = _('No LOINC codes selected.')
4639 self._PRW_loinc.SetFocus()
4640
4641 if self._TCTRL_description.GetValue().strip() == '':
4642 validity = False
4643 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False)
4644 self._TCTRL_description.SetFocus()
4645 else:
4646 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True)
4647
4648 return validity
4649
4650
4659
4660
4662 self.data['description'] = self._TCTRL_description.GetValue().strip()
4663 self.data['comment'] = self._TCTRL_comment.GetValue().strip()
4664 self.data.save()
4665 if self.__loincs is not None:
4666 self.data.included_loincs = self.__loincs
4667 return True
4668
4669
4671 self._TCTRL_description.SetValue('')
4672 self._TCTRL_comment.SetValue('')
4673 self._PRW_loinc.SetText('', None)
4674 self._LBL_loinc.SetLabel('')
4675 self.__loincs = None
4676 self.__refresh_loinc_list()
4677
4678 self._TCTRL_description.SetFocus()
4679
4680
4682 self._refresh_as_new()
4683
4684
4686 self._TCTRL_description.SetValue(self.data['description'])
4687 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
4688 self._PRW_loinc.SetText('', None)
4689 self._LBL_loinc.SetLabel('')
4690 self.__loincs = self.data['loincs']
4691 self.__refresh_loinc_list()
4692
4693 self._PRW_loinc.SetFocus()
4694
4695
4696
4697
4699 loinc = self._PRW_loinc.GetData()
4700 if loinc is None:
4701 self._LBL_loinc.SetLabel('')
4702 return
4703 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4704 if loinc_detail is None:
4705 loinc_str = _('no LOINC details found')
4706 else:
4707 loinc_str = '%s: %s%s' % (
4708 loinc,
4709 loinc_detail['term'],
4710 gmTools.coalesce(loinc_detail['example_units'], '', ' (%s)')
4711 )
4712 self._LBL_loinc.SetLabel(loinc_str)
4713
4714
4736
4737
4741
4742
4744 loincs2remove = self._LCTRL_loincs.selected_item_data
4745 if loincs2remove is None:
4746 return
4747 for loinc in loincs2remove:
4748 try:
4749 while True:
4750 self.__loincs.remove(loinc[0])
4751 except ValueError:
4752 pass
4753 self.__refresh_loinc_list()
4754
4755
4756
4757
4758 if __name__ == '__main__':
4759
4760 from Gnumed.pycommon import gmLog2
4761 from Gnumed.wxpython import gmPatSearchWidgets
4762
4763 gmI18N.activate_locale()
4764 gmI18N.install_domain()
4765 gmDateTime.init()
4766
4767
4775
4783
4784
4785
4786
4787
4788
4789
4790 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
4791
4792 test_test_ea_pnl()
4793
4794
4795
4796