1 """GNUmed measurement widgets."""
2
3 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
4 __license__ = "GPL"
5
6
7 import sys
8 import logging
9 import datetime as pyDT
10 import decimal
11 import os
12 import subprocess
13 import io
14 import os.path
15
16
17 import wx
18 import wx.grid
19 import wx.adv as wxh
20
21
22 if __name__ == '__main__':
23 sys.path.insert(0, '../../')
24 from Gnumed.pycommon import gmTools
25 from Gnumed.pycommon import gmNetworkTools
26 from Gnumed.pycommon import gmI18N
27 from Gnumed.pycommon import gmShellAPI
28 from Gnumed.pycommon import gmCfg
29 from Gnumed.pycommon import gmDateTime
30 from Gnumed.pycommon import gmMatchProvider
31 from Gnumed.pycommon import gmDispatcher
32 from Gnumed.pycommon import gmMimeLib
33
34 from Gnumed.business import gmPerson
35 from Gnumed.business import gmStaff
36 from Gnumed.business import gmPathLab
37 from Gnumed.business import gmPraxis
38 from Gnumed.business import gmLOINC
39 from Gnumed.business import gmForms
40 from Gnumed.business import gmPersonSearch
41 from Gnumed.business import gmOrganization
42 from Gnumed.business import gmHL7
43 from Gnumed.business import gmIncomingData
44 from Gnumed.business import gmDocuments
45
46 from Gnumed.wxpython import gmRegetMixin
47 from Gnumed.wxpython import gmPlugin
48 from Gnumed.wxpython import gmEditArea
49 from Gnumed.wxpython import gmPhraseWheel
50 from Gnumed.wxpython import gmListWidgets
51 from Gnumed.wxpython import gmGuiHelpers
52 from Gnumed.wxpython import gmAuthWidgets
53 from Gnumed.wxpython import gmOrganizationWidgets
54 from Gnumed.wxpython import gmEMRStructWidgets
55 from Gnumed.wxpython import gmCfgWidgets
56 from Gnumed.wxpython import gmDocumentWidgets
57
58
59 _log = logging.getLogger('gm.ui')
60
61
62
63
65
66 if parent is None:
67 parent = wx.GetApp().GetTopWindow()
68
69
70 paths = gmTools.gmPaths()
71 dlg = wx.FileDialog (
72 parent = parent,
73 message = _('Show HL7 file:'),
74
75 defaultDir = os.path.join(paths.home_dir, 'gnumed'),
76 wildcard = "hl7 files|*.hl7|HL7 files|*.HL7|all files|*",
77 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
78 )
79 choice = dlg.ShowModal()
80 hl7_name = dlg.GetPath()
81 dlg.DestroyLater()
82 if choice != wx.ID_OK:
83 return False
84
85 formatted_name = gmHL7.format_hl7_file (
86 hl7_name,
87 skip_empty_fields = True,
88 return_filename = True,
89 fix_hl7 = True
90 )
91 gmMimeLib.call_viewer_on_file(aFile = formatted_name, block = False)
92 return True
93
94
96
97 if parent is None:
98 parent = wx.GetApp().GetTopWindow()
99
100
101 paths = gmTools.gmPaths()
102 dlg = wx.FileDialog (
103 parent = parent,
104 message = _('Extract HL7 from XML file:'),
105
106 defaultDir = os.path.join(paths.home_dir, 'gnumed'),
107 wildcard = "xml files|*.xml|XML files|*.XML|all files|*",
108 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
109 )
110 choice = dlg.ShowModal()
111 xml_name = dlg.GetPath()
112 dlg.DestroyLater()
113 if choice != wx.ID_OK:
114 return False
115
116 target_dir = os.path.split(xml_name)[0]
117 xml_path = './/Message'
118 hl7_name = gmHL7.extract_HL7_from_XML_CDATA(xml_name, xml_path, target_dir = target_dir)
119 if hl7_name is None:
120 gmGuiHelpers.gm_show_error (
121 title = _('Extracting HL7 from XML file'),
122 error = (
123 'Cannot unwrap HL7 data from XML file\n'
124 '\n'
125 ' [%s]\n'
126 '\n'
127 '(CDATA of [%s] nodes)'
128 ) % (
129 xml_name,
130 xml_path
131 )
132 )
133 return False
134
135 gmDispatcher.send(signal = 'statustext', msg = _('Unwrapped HL7 into [%s] from [%s].') % (hl7_name, xml_name), beep = False)
136 return True
137
138
140
141 if parent is None:
142 parent = wx.GetApp().GetTopWindow()
143
144 paths = gmTools.gmPaths()
145 dlg = wx.FileDialog (
146 parent = parent,
147 message = _('Select HL7 file for staging:'),
148
149 defaultDir = os.path.join(paths.home_dir, 'gnumed'),
150 wildcard = ".hl7 files|*.hl7|.HL7 files|*.HL7|all files|*",
151 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
152 )
153 choice = dlg.ShowModal()
154 hl7_name = dlg.GetPath()
155 dlg.DestroyLater()
156 if choice != wx.ID_OK:
157 return False
158
159 target_dir = os.path.join(paths.home_dir, '.gnumed', 'hl7')
160 success, PID_names = gmHL7.split_hl7_file(hl7_name, target_dir = target_dir, encoding = 'utf8')
161 if not success:
162 gmGuiHelpers.gm_show_error (
163 title = _('Staging HL7 file'),
164 error = _(
165 'There was a problem with splitting the HL7 file\n'
166 '\n'
167 ' %s'
168 ) % hl7_name
169 )
170 return False
171
172 failed_files = []
173 for PID_name in PID_names:
174 if not gmHL7.stage_single_PID_hl7_file(PID_name, source = _('generic'), encoding = 'utf8'):
175 failed_files.append(PID_name)
176 if len(failed_files) > 0:
177 gmGuiHelpers.gm_show_error (
178 title = _('Staging HL7 file'),
179 error = _(
180 'There was a problem with staging the following files\n'
181 '\n'
182 ' %s'
183 ) % '\n '.join(failed_files)
184 )
185 return False
186
187 gmDispatcher.send(signal = 'statustext', msg = _('Staged HL7 from [%s].') % hl7_name, beep = False)
188 return True
189
190
212
213 def import_hl7(staged_item):
214 if staged_item is None:
215 return False
216 if 'HL7' not in staged_item['data_type']:
217 return False
218 unset_identity_on_error = False
219 if staged_item['pk_identity_disambiguated'] is None:
220 pat = gmPerson.gmCurrentPatient()
221 if pat.connected:
222 answer = gmGuiHelpers.gm_show_question (
223 title = _('Importing HL7 data'),
224 question = _(
225 'There has not been a patient explicitely associated\n'
226 'with this chunk of HL7 data. However, the data file\n'
227 'contains the following patient identification information:\n'
228 '\n'
229 ' %s\n'
230 '\n'
231 'Do you want to import the HL7 under the current patient ?\n'
232 '\n'
233 ' %s\n'
234 '\n'
235 'Selecting [NO] makes GNUmed try to find a patient matching the HL7 data.\n'
236 ) % (
237 staged_item.patient_identification,
238 pat['description_gender']
239 ),
240 cancel_button = True
241 )
242 if answer is None:
243 return False
244 if answer is True:
245 unset_identity_on_error = True
246 staged_item['pk_identity_disambiguated'] = pat.ID
247
248 success, log_name = gmHL7.process_staged_single_PID_hl7_file(staged_item)
249 if success:
250 return True
251
252 if unset_identity_on_error:
253 staged_item['pk_identity_disambiguated'] = None
254 staged_item.save()
255
256 gmGuiHelpers.gm_show_error (
257 error = _('Error processing HL7 data.'),
258 title = _('Processing staged HL7 data.')
259 )
260 return False
261
262
263 def delete(staged_item):
264 if staged_item is None:
265 return False
266 do_delete = gmGuiHelpers.gm_show_question (
267 title = _('Deleting incoming data'),
268 question = _(
269 'Do you really want to delete the incoming data ?\n'
270 '\n'
271 'Note that deletion is not reversible.'
272 )
273 )
274 if not do_delete:
275 return False
276 return gmIncomingData.delete_incoming_data(pk_incoming_data = staged_item['pk_incoming_data_unmatched'])
277
278 def refresh(lctrl):
279 incoming = gmIncomingData.get_incoming_data()
280 items = [ [
281 gmTools.coalesce(i['data_type'], ''),
282 '%s, %s (%s) %s' % (
283 gmTools.coalesce(i['lastnames'], ''),
284 gmTools.coalesce(i['firstnames'], ''),
285 gmDateTime.pydt_strftime(dt = i['dob'], format = '%Y %b %d', accuracy = gmDateTime.acc_days, none_str = _('unknown DOB')),
286 gmTools.coalesce(i['gender'], '')
287 ),
288 gmTools.coalesce(i['external_data_id'], ''),
289 i['pk_incoming_data_unmatched']
290 ] for i in incoming ]
291 lctrl.set_string_items(items)
292 lctrl.set_data(incoming)
293
294 gmListWidgets.get_choices_from_list (
295 parent = parent,
296 msg = None,
297 caption = _('Showing unmatched incoming data'),
298 columns = [ _('Type'), _('Identification'), _('Reference'), '#' ],
299 single_selection = True,
300 can_return_empty = False,
301 ignore_OK_button = True,
302 refresh_callback = refresh,
303
304
305 delete_callback = delete,
306 left_extra_button = [_('Show'), _('Show formatted HL7'), show_hl7],
307 middle_extra_button = [_('Import'), _('Import HL7 data into patient chart'), import_hl7]
308
309 )
310
311
312
313
343
344
345 -def edit_measurement(parent=None, measurement=None, single_entry=False, presets=None):
359
360
361 -def manage_measurements(parent=None, single_selection=False, emr=None, measurements2manage=None, message=None):
373
374
375 def delete(measurement):
376 gmPathLab.delete_test_result(result = measurement)
377 return True
378
379
380 def do_review(lctrl):
381 data = lctrl.get_selected_item_data()
382 if len(data) == 0:
383 return
384
385 return review_tests(parent = parent, tests = data)
386
387
388 def do_plot(lctrl):
389 data = lctrl.get_selected_item_data()
390 if len(data) == 0:
391 return
392
393 return plot_measurements(parent = parent, tests = data)
394
395
396 def get_tooltip(measurement):
397 return measurement.format(with_review=True, with_evaluation=True, with_ranges=True)
398
399
400 def refresh(lctrl):
401 if measurements2manage is None:
402 results = emr.get_test_results(order_by = 'clin_when DESC, unified_abbrev, unified_name')
403 else:
404 results = measurements2manage
405 items = [ [
406 gmDateTime.pydt_strftime (
407 r['clin_when'],
408 '%Y %b %d %H:%M',
409 accuracy = gmDateTime.acc_minutes
410 ),
411 r['unified_abbrev'],
412 '%s%s%s%s' % (
413 gmTools.bool2subst (
414 boolean = (not r['reviewed'] or (not r['review_by_you'] and r['you_are_responsible'])),
415 true_return = 'u' + gmTools.u_writing_hand,
416 false_return = ''
417 ),
418 r['unified_val'],
419 gmTools.coalesce(r['val_unit'], '', ' %s'),
420 gmTools.coalesce(r['abnormality_indicator'], '', ' %s')
421 ),
422 r['unified_name'],
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 return gmListWidgets.get_choices_from_list (
431 parent = parent,
432 msg = message,
433 caption = _('Showing test results.'),
434 columns = [ _('When'), _('Abbrev'), _('Value'), _('Name'), _('Comment'), '#' ],
435 single_selection = single_selection,
436 can_return_empty = False,
437 refresh_callback = refresh,
438 edit_callback = edit,
439 new_callback = edit,
440 delete_callback = delete,
441 list_tooltip_callback = get_tooltip,
442 left_extra_button = (_('Review'), _('Review current selection'), do_review, True),
443 middle_extra_button = (_('Plot'), _('Plot current selection'), do_plot, True)
444 )
445
446
464
465
496
497
499
500 option = 'form_templates.default_gnuplot_template'
501
502 dbcfg = gmCfg.cCfgSQL()
503
504
505 default_template_name = dbcfg.get2 (
506 option = option,
507 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
508 bias = 'user'
509 )
510
511
512 if default_template_name is None:
513 gmDispatcher.send('statustext', msg = _('No default Gnuplot template configured.'), beep = False)
514 default_template = configure_default_gnuplot_template(parent = parent)
515
516 if default_template is None:
517 gmGuiHelpers.gm_show_error (
518 aMessage = _('There is no default Gnuplot one-type script template configured.'),
519 aTitle = _('Plotting test results')
520 )
521 return None
522 return default_template
523
524
525
526 try:
527 name, ver = default_template_name.split(' - ')
528 except Exception:
529
530 _log.exception('problem splitting Gnuplot script template name [%s]', default_template_name)
531 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading Gnuplot script template.'), beep = True)
532 return None
533
534 default_template = gmForms.get_form_template(name_long = name, external_version = ver)
535 if default_template is None:
536 default_template = configure_default_gnuplot_template(parent = parent)
537
538 if default_template is None:
539 gmGuiHelpers.gm_show_error (
540 aMessage = _('Cannot load default Gnuplot script template [%s - %s]') % (name, ver),
541 aTitle = _('Plotting test results')
542 )
543 return None
544
545 return default_template
546
547
548 -def plot_measurements(parent=None, tests=None, format=None, show_year = True, use_default_template=False):
586
587
588 -def plot_adjacent_measurements(parent=None, test=None, format=None, show_year=True, plot_singular_result=True, use_default_template=False):
589
590 earlier, later = test.get_adjacent_results(desired_earlier_results = 2, desired_later_results = 2)
591 results2plot = []
592 if earlier is not None:
593 results2plot.extend(earlier)
594 results2plot.append(test)
595 if later is not None:
596 results2plot.extend(later)
597 if len(results2plot) == 1:
598 if not plot_singular_result:
599 return
600 plot_measurements (
601 parent = parent,
602 tests = results2plot,
603 format = format,
604 show_year = show_year,
605 use_default_template = use_default_template
606 )
607
608
609
610
611
612
613
614
615
616
617
618
619 from Gnumed.wxGladeWidgets import wxgLabRelatedDocumentsPnl
620
764
765
766 from Gnumed.wxGladeWidgets import wxgMeasurementsAsListPnl
767
768 -class cMeasurementsAsListPnl(wxgMeasurementsAsListPnl.wxgMeasurementsAsListPnl, gmRegetMixin.cRegetOnPaintMixin):
769 """A class for displaying all measurement results as a simple list.
770
771 - operates on a cPatient instance handed to it and NOT on the currently active patient
772 """
782
783
784
785
787 self._LCTRL_results.set_columns([_('When'), _('Test'), _('Result'), _('Reference')])
788 self._LCTRL_results.edit_callback = self._on_edit
789 self._PNL_related_documents.lab_reference = None
790
791
794
795
797 if self.__patient is None:
798 self._LCTRL_results.set_string_items([])
799 self._TCTRL_measurements.SetValue('')
800 self._PNL_related_documents.lab_reference = None
801 return
802
803 results = self.__patient.emr.get_test_results(order_by = 'clin_when DESC, unified_abbrev, unified_name')
804 items = []
805 data = []
806 for r in results:
807 range_info = gmTools.coalesce (
808 r.formatted_clinical_range,
809 r.formatted_normal_range
810 )
811 review = gmTools.bool2subst (
812 r['reviewed'],
813 '',
814 ' ' + gmTools.u_writing_hand,
815 ' ' + gmTools.u_writing_hand
816 )
817 items.append ([
818 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes),
819 r['abbrev_tt'],
820 '%s%s%s%s' % (
821 gmTools.strip_empty_lines(text = r['unified_val'])[0],
822 gmTools.coalesce(r['val_unit'], '', ' %s'),
823 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
824 review
825 ),
826 gmTools.coalesce(range_info, '')
827 ])
828 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
829
830 self._LCTRL_results.set_string_items(items)
831 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
832 self._LCTRL_results.set_data(data)
833 if len(items) > 0:
834 self._LCTRL_results.Select(idx = 0, on = 1)
835 self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted'])
836
837 self._LCTRL_results.SetFocus()
838
839
846
847
848
849
851 if self.__patient is None:
852 return True
853
854 if kwds['pk_identity'] is not None:
855 if kwds['pk_identity'] != self.__patient.ID:
856 return True
857
858 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
859 return True
860
861 self._schedule_data_reget()
862 return True
863
864
870
871
872
873
875 self.__repopulate_ui()
876 return True
877
878
879
880
882 return self.__patient
883
885 if (self.__patient is None) and (patient is None):
886 return
887 if (self.__patient is None) or (patient is None):
888 self.__patient = patient
889 self._schedule_data_reget()
890 return
891 if self.__patient.ID == patient.ID:
892 return
893 self.__patient = patient
894 self._schedule_data_reget()
895
896 patient = property(_get_patient, _set_patient)
897
898
899 from Gnumed.wxGladeWidgets import wxgMeasurementsByDayPnl
900
901 -class cMeasurementsByDayPnl(wxgMeasurementsByDayPnl.wxgMeasurementsByDayPnl, gmRegetMixin.cRegetOnPaintMixin):
902 """A class for displaying measurement results as a list partitioned by day.
903
904 - operates on a cPatient instance handed to it and NOT on the currently active patient
905 """
916
917
918
919
927
928
931
932
938
939
962
963
970
971
973 result = self._LCTRL_results.get_item_data(item_idx = 0)['data']
974 presets = {
975 'clin_when': {'data': result['clin_when']},
976 'pk_episode': {'data': result['pk_episode']}
977 }
978 added = edit_measurement(parent = self, measurement = None, single_entry = False, presets = presets)
979 if added:
980 self.__repopulate_ui()
981 self._LCTRL_results.SetFocus()
982
983
998
999
1000
1001
1003 if self.__patient is None:
1004 return True
1005
1006 if kwds['pk_identity'] is not None:
1007 if kwds['pk_identity'] != self.__patient.ID:
1008 return True
1009
1010 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1011 return True
1012
1013 self._schedule_data_reget()
1014 return True
1015
1016
1018 event.Skip()
1019
1020 day = self._LCTRL_days.get_item_data(item_idx = event.Index)['clin_when_day']
1021 results = self.__patient.emr.get_results_for_day(timestamp = day)
1022 items = []
1023 data = []
1024 for r in results:
1025 range_info = gmTools.coalesce (
1026 r.formatted_clinical_range,
1027 r.formatted_normal_range
1028 )
1029 review = gmTools.bool2subst (
1030 r['reviewed'],
1031 '',
1032 ' ' + gmTools.u_writing_hand,
1033 ' ' + gmTools.u_writing_hand
1034 )
1035 items.append ([
1036 gmDateTime.pydt_strftime(r['clin_when'], '%H:%M'),
1037 r['abbrev_tt'],
1038 '%s%s%s%s' % (
1039 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1040 gmTools.coalesce(r['val_unit'], '', ' %s'),
1041 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1042 review
1043 ),
1044 gmTools.coalesce(range_info, '')
1045 ])
1046 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
1047
1048 self._LCTRL_results.set_string_items(items)
1049 self._LCTRL_results.set_column_label(1, _('Test (%s%s)') % (gmTools.u_sum, len(items)))
1050 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1051 self._LCTRL_results.set_data(data)
1052 self._LCTRL_results.Select(idx = 0, on = 1)
1053
1054
1060
1061
1062
1063
1065 self.__repopulate_ui()
1066 return True
1067
1068
1069
1070
1072 return self.__patient
1073
1075 if (self.__patient is None) and (patient is None):
1076 return
1077 if patient is None:
1078 self.__patient = None
1079 self.__clear()
1080 return
1081 if self.__patient is None:
1082 self.__patient = patient
1083 self._schedule_data_reget()
1084 return
1085 if self.__patient.ID == patient.ID:
1086 return
1087 self.__patient = patient
1088 self._schedule_data_reget()
1089
1090 patient = property(_get_patient, _set_patient)
1091
1092
1093 from Gnumed.wxGladeWidgets import wxgMeasurementsByIssuePnl
1094
1095 -class cMeasurementsByIssuePnl(wxgMeasurementsByIssuePnl.wxgMeasurementsByIssuePnl, gmRegetMixin.cRegetOnPaintMixin):
1096 """A class for displaying measurement results as a list partitioned by issue/episode.
1097
1098 - operates on a cPatient instance handed to it and NOT on the currently active patient
1099 """
1109
1110
1111
1112
1114 self._LCTRL_issues.set_columns([_('Problem')])
1115 self._LCTRL_results.set_columns([_('When'), _('Test'), _('Result'), _('Reference')])
1116 self._PNL_related_documents.lab_reference = None
1117
1118
1124
1125
1131
1132
1152
1153
1160
1161
1162
1163
1165 if self.__patient is None:
1166 return True
1167
1168 if kwds['pk_identity'] is not None:
1169 if kwds['pk_identity'] != self.__patient.ID:
1170 return True
1171
1172 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1173 return True
1174
1175 self._schedule_data_reget()
1176 return True
1177
1178
1180 event.Skip()
1181
1182 pk_issue = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_issue']
1183 if pk_issue is None:
1184 pk_episode = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_episode']
1185 results = self.__patient.emr.get_results_for_episode(pk_episode = pk_episode)
1186 else:
1187 results = self.__patient.emr.get_results_for_issue(pk_health_issue = pk_issue)
1188 items = []
1189 data = []
1190 for r in results:
1191 range_info = gmTools.coalesce (
1192 r.formatted_clinical_range,
1193 r.formatted_normal_range
1194 )
1195 review = gmTools.bool2subst (
1196 r['reviewed'],
1197 '',
1198 ' ' + gmTools.u_writing_hand,
1199 ' ' + gmTools.u_writing_hand
1200 )
1201 items.append ([
1202 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M'),
1203 r['abbrev_tt'],
1204 '%s%s%s%s' % (
1205 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1206 gmTools.coalesce(r['val_unit'], '', ' %s'),
1207 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1208 review
1209 ),
1210 gmTools.coalesce(range_info, '')
1211 ])
1212 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
1213
1214 self._LCTRL_results.set_string_items(items)
1215 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1216 self._LCTRL_results.set_data(data)
1217 self._LCTRL_results.Select(idx = 0, on = 1)
1218 self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted'])
1219
1220
1226
1227
1228
1229
1231 self.__repopulate_ui()
1232 return True
1233
1234
1235
1236
1238 return self.__patient
1239
1241 if (self.__patient is None) and (patient is None):
1242 return
1243 if patient is None:
1244 self.__patient = None
1245 self.__clear()
1246 return
1247 if self.__patient is None:
1248 self.__patient = patient
1249 self._schedule_data_reget()
1250 return
1251 if self.__patient.ID == patient.ID:
1252 return
1253 self.__patient = patient
1254 self._schedule_data_reget()
1255
1256 patient = property(_get_patient, _set_patient)
1257
1258
1259 from Gnumed.wxGladeWidgets import wxgMeasurementsByBatteryPnl
1260
1262 """A grid class for displaying measurement results filtered by battery/panel.
1263
1264 - operates on a cPatient instance handed to it and NOT on the currently active patient
1265 """
1275
1276
1277
1278
1281
1282
1288
1289
1291 self._GRID_results_battery.patient = self.__patient
1292 return True
1293
1294
1296 if panel is None:
1297 self._TCTRL_panel_comment.SetValue('')
1298 self._GRID_results_battery.panel_to_show = None
1299 else:
1300 pnl = self._PRW_panel.GetData(as_instance = True)
1301 self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
1302 pnl['comment'],
1303 ''
1304 ))
1305 self._GRID_results_battery.panel_to_show = pnl
1306
1307
1308
1310 self._TCTRL_panel_comment.SetValue('')
1311 if self._PRW_panel.GetValue().strip() == '':
1312 self._GRID_results_battery.panel_to_show = None
1313
1314
1315
1316
1317
1319 if self.__patient is None:
1320 return True
1321
1322 if kwds['pk_identity'] is not None:
1323 if kwds['pk_identity'] != self.__patient.ID:
1324 return True
1325
1326 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1327 return True
1328
1329 self._schedule_data_reget()
1330 return True
1331
1332
1335
1336
1338 wx.CallAfter(self.__on_panel_selected, panel=panel)
1339
1340
1342 wx.CallAfter(self.__on_panel_selection_modified)
1343
1344
1345
1346
1348 self.__repopulate_ui()
1349 return True
1350
1351
1352
1353
1355 return self.__patient
1356
1358 if (self.__patient is None) and (patient is None):
1359 return
1360 if (self.__patient is None) or (patient is None):
1361 self.__patient = patient
1362 self._schedule_data_reget()
1363 return
1364 if self.__patient.ID == patient.ID:
1365 return
1366 self.__patient = patient
1367 self._schedule_data_reget()
1368
1369 patient = property(_get_patient, _set_patient)
1370
1371
1372 from Gnumed.wxGladeWidgets import wxgMeasurementsAsMostRecentListPnl
1373
1375 """A list ctrl class for displaying measurement results.
1376
1377 - most recent results
1378 - possibly filtered by battery/panel
1379
1380 - operates on a cPatient instance handed to it and NOT on the currently active patient
1381 """
1391
1392
1393
1394
1396 self._LCTRL_results.set_columns([_('Test'), _('Result'), _('When'), _('Range')])
1397 self._CHBOX_show_missing.Disable()
1398 self._PNL_related_documents.lab_reference = None
1399
1400
1409
1410
1412
1413 self._TCTRL_details.SetValue('')
1414 self._PNL_related_documents.lab_reference = None
1415 if self.__patient is None:
1416 self._LCTRL_results.remove_items_safely()
1417 return
1418
1419 pnl = self._PRW_panel.GetData(as_instance = True)
1420 if pnl is None:
1421 results = gmPathLab.get_most_recent_result_for_test_types (
1422 pk_patient = self.__patient.ID,
1423 consider_meta_type = True
1424 )
1425 else:
1426 results = pnl.get_most_recent_results (
1427 pk_patient = self.__patient.ID,
1428
1429 group_by_meta_type = True,
1430 include_missing = self._CHBOX_show_missing.IsChecked()
1431 )
1432 items = []
1433 data = []
1434 for r in results:
1435 if isinstance(r, gmPathLab.cTestResult):
1436 result_type = gmTools.coalesce (
1437 value2test = r['pk_meta_test_type'],
1438 return_instead = r['abbrev_tt'],
1439 value2return = '%s%s' % (gmTools.u_sum, r['abbrev_meta'])
1440 )
1441 review = gmTools.bool2subst (
1442 r['reviewed'],
1443 '',
1444 ' ' + gmTools.u_writing_hand,
1445 ' ' + gmTools.u_writing_hand
1446 )
1447 result_val = '%s%s%s%s' % (
1448 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1449 gmTools.coalesce(r['val_unit'], '', ' %s'),
1450 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1451 review
1452 )
1453 result_when = _('%s ago (%s)') % (
1454 gmDateTime.format_interval_medically(interval = gmDateTime.pydt_now_here() - r['clin_when']),
1455 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes)
1456 )
1457 range_info = gmTools.coalesce (
1458 r.formatted_clinical_range,
1459 r.formatted_normal_range
1460 )
1461 tt = r.format(with_source_data = True)
1462 else:
1463 result_type = r
1464 result_val = _('missing')
1465 loinc_data = gmLOINC.loinc2data(r)
1466 if loinc_data is None:
1467 result_when = _('LOINC not found')
1468 tt = u''
1469 else:
1470 result_when = loinc_data['term']
1471 tt = gmLOINC.format_loinc(r)
1472 range_info = None
1473 items.append([result_type, result_val, result_when, gmTools.coalesce(range_info, '')])
1474 data.append({'data': r, 'formatted': tt})
1475
1476 self._LCTRL_results.set_string_items(items)
1477 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1478 self._LCTRL_results.set_data(data)
1479
1480 if len(items) > 0:
1481 self._LCTRL_results.Select(idx = 0, on = 1)
1482 self._LCTRL_results.SetFocus()
1483
1484 return True
1485
1486
1488 if panel is None:
1489 self._TCTRL_panel_comment.SetValue('')
1490 self._CHBOX_show_missing.Disable()
1491 else:
1492 pnl = self._PRW_panel.GetData(as_instance = True)
1493 self._TCTRL_panel_comment.SetValue(gmTools.coalesce(pnl['comment'], ''))
1494 self.__repopulate_ui()
1495 self._CHBOX_show_missing.Enable()
1496
1497
1499 self._TCTRL_panel_comment.SetValue('')
1500 if self._PRW_panel.Value.strip() == u'':
1501 self.__repopulate_ui()
1502 self._CHBOX_show_missing.Disable()
1503
1504
1505
1506
1508 if self.__patient is None:
1509 return True
1510
1511 if kwds['pk_identity'] is not None:
1512 if kwds['pk_identity'] != self.__patient.ID:
1513 return True
1514
1515 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results', 'clin.test_panel']:
1516 return True
1517
1518 self._schedule_data_reget()
1519 return True
1520
1521
1524
1525
1527 wx.CallAfter(self.__on_panel_selected, panel = panel)
1528
1529
1531 wx.CallAfter(self.__on_panel_selection_modified)
1532
1533
1542
1543
1551
1552
1554 event.Skip()
1555
1556 if self._PRW_panel.GetData(as_instance = False) is None:
1557 return
1558 self.__repopulate_ui()
1559
1560
1561
1562
1564 self.__repopulate_ui()
1565 return True
1566
1567
1568
1569
1571 return self.__patient
1572
1574 if (self.__patient is None) and (patient is None):
1575 return
1576 if (self.__patient is None) or (patient is None):
1577 self.__patient = patient
1578 self._schedule_data_reget()
1579 return
1580 if self.__patient.ID == patient.ID:
1581 return
1582 self.__patient = patient
1583 self._schedule_data_reget()
1584
1585 patient = property(_get_patient, _set_patient)
1586
1587
1588 from Gnumed.wxGladeWidgets import wxgMeasurementsAsTablePnl
1589
1590 -class cMeasurementsAsTablePnl(wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl, gmRegetMixin.cRegetOnPaintMixin):
1591 """A panel for holding a grid displaying all measurement results.
1592
1593 - operates on a cPatient instance handed to it and NOT on the currently active patient
1594 """
1604
1605
1606
1607
1609 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
1610
1611 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
1612 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
1613
1614 item = self.__action_button_popup.Append(-1, _('Plot'))
1615 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625 item = self.__action_button_popup.Append(-1, _('&Delete'))
1626 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
1627
1628
1629
1630
1631 self._GRID_results_all.show_by_panel = False
1632
1633
1636
1637
1639 self._GRID_results_all.patient = self.__patient
1640
1641 self.Layout()
1642 return True
1643
1644
1647
1648
1651
1652
1655
1656
1657
1658
1660 if self.__patient is None:
1661 return True
1662
1663 if kwds['pk_identity'] is not None:
1664 if kwds['pk_identity'] != self.__patient.ID:
1665 return True
1666
1667 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1668 return True
1669
1670 self._schedule_data_reget()
1671 return True
1672
1673
1676
1677
1681
1682
1685
1686
1692
1693
1694
1695
1697 self.__repopulate_ui()
1698 return True
1699
1700
1701
1702
1704 return self.__patient
1705
1707 if (self.__patient is None) and (patient is None):
1708 return
1709 if (self.__patient is None) or (patient is None):
1710 self.__patient = patient
1711 self._schedule_data_reget()
1712 return
1713 if self.__patient.ID == patient.ID:
1714 return
1715 self.__patient = patient
1716 self._schedule_data_reget()
1717
1718 patient = property(_get_patient, _set_patient)
1719
1720
1721
1722
1724 """Notebook displaying measurements pages:
1725
1726 - by test battery
1727 - by day
1728 - by issue/episode
1729 - most-recent list, perhaps by panel
1730 - full grid
1731 - full list
1732
1733 Used as a main notebook plugin page.
1734
1735 Operates on the active patient.
1736 """
1737
1752
1753
1754
1755
1757 for page_idx in range(self.GetPageCount()):
1758 page = self.GetPage(page_idx)
1759 page.patient = None
1760
1761
1762 - def _post_patient_selection(self, **kwds):
1763 for page_idx in range(self.GetPageCount()):
1764 page = self.GetPage(page_idx)
1765 page.patient = self.__patient.patient
1766
1767
1768
1769
1771 if self.__patient.connected:
1772 pat = self.__patient.patient
1773 else:
1774 pat = None
1775 for page_idx in range(self.GetPageCount()):
1776 page = self.GetPage(page_idx)
1777 page.patient = pat
1778
1779 return True
1780
1781
1782
1783
1785
1786
1787 new_page = cMeasurementsByDayPnl(self, -1)
1788 new_page.patient = None
1789 self.AddPage (
1790 page = new_page,
1791 text = _('Days'),
1792 select = True
1793 )
1794
1795
1796 new_page = cMeasurementsByIssuePnl(self, -1)
1797 new_page.patient = None
1798 self.AddPage (
1799 page = new_page,
1800 text = _('Problems'),
1801 select = False
1802 )
1803
1804
1805 new_page = cMeasurementsByBatteryPnl(self, -1)
1806 new_page.patient = None
1807 self.AddPage (
1808 page = new_page,
1809 text = _('Panels'),
1810 select = False
1811 )
1812
1813
1814 new_page = cMeasurementsAsMostRecentListPnl(self, -1)
1815 new_page.patient = None
1816 self.AddPage (
1817 page = new_page,
1818 text = _('Most recent'),
1819 select = False
1820 )
1821
1822
1823 new_page = cMeasurementsAsTablePnl(self, -1)
1824 new_page.patient = None
1825 self.AddPage (
1826 page = new_page,
1827 text = _('Table'),
1828 select = False
1829 )
1830
1831
1832 new_page = cMeasurementsAsListPnl(self, -1)
1833 new_page.patient = None
1834 self.AddPage (
1835 page = new_page,
1836 text = _('List'),
1837 select = False
1838 )
1839
1840
1841
1842
1844 return self.__patient
1845
1847 self.__patient = patient
1848 if self.__patient.connected:
1849 pat = self.__patient.patient
1850 else:
1851 pat = None
1852 for page_idx in range(self.GetPageCount()):
1853 page = self.GetPage(page_idx)
1854 page.patient = pat
1855
1856 patient = property(_get_patient, _set_patient)
1857
1858
1860 """A grid class for displaying measurement results.
1861
1862 - operates on a cPatient instance handed to it
1863 - does NOT listen to the currently active patient
1864 - thereby it can display any patient at any time
1865 """
1866
1867
1868
1869
1870
1872
1873 wx.grid.Grid.__init__(self, *args, **kwargs)
1874
1875 self.__patient = None
1876 self.__panel_to_show = None
1877 self.__show_by_panel = False
1878 self.__cell_data = {}
1879 self.__row_label_data = []
1880 self.__col_label_data = []
1881
1882 self.__prev_row = None
1883 self.__prev_col = None
1884 self.__prev_label_row = None
1885 self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::'))
1886
1887 self.__init_ui()
1888 self.__register_events()
1889
1890
1891
1892
1894 if not self.IsSelection():
1895 gmDispatcher.send(signal = 'statustext', msg = _('No results selected for deletion.'))
1896 return True
1897
1898 selected_cells = self.get_selected_cells()
1899 if len(selected_cells) > 20:
1900 results = None
1901 msg = _(
1902 'There are %s results marked for deletion.\n'
1903 '\n'
1904 'Are you sure you want to delete these results ?'
1905 ) % len(selected_cells)
1906 else:
1907 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1908 txt = '\n'.join([ '%s %s (%s): %s %s%s' % (
1909 r['clin_when'].strftime('%x %H:%M'),
1910 r['unified_abbrev'],
1911 r['unified_name'],
1912 r['unified_val'],
1913 r['val_unit'],
1914 gmTools.coalesce(r['abnormality_indicator'], '', ' (%s)')
1915 ) for r in results
1916 ])
1917 msg = _(
1918 'The following results are marked for deletion:\n'
1919 '\n'
1920 '%s\n'
1921 '\n'
1922 'Are you sure you want to delete these results ?'
1923 ) % txt
1924
1925 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
1926 self,
1927 -1,
1928 caption = _('Deleting test results'),
1929 question = msg,
1930 button_defs = [
1931 {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False},
1932 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True}
1933 ]
1934 )
1935 decision = dlg.ShowModal()
1936
1937 if decision == wx.ID_YES:
1938 if results is None:
1939 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1940 for result in results:
1941 gmPathLab.delete_test_result(result)
1942
1943
1945 if not self.IsSelection():
1946 gmDispatcher.send(signal = 'statustext', msg = _('Cannot sign results. No results selected.'))
1947 return True
1948
1949 selected_cells = self.get_selected_cells()
1950 tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1951
1952 return review_tests(parent = self, tests = tests)
1953
1954
1956
1957 if not self.IsSelection():
1958 gmDispatcher.send(signal = 'statustext', msg = _('Cannot plot results. No results selected.'))
1959 return True
1960
1961 tests = self.__cells_to_data (
1962 cells = self.get_selected_cells(),
1963 exclude_multi_cells = False,
1964 auto_include_multi_cells = True
1965 )
1966
1967 plot_measurements(parent = self, tests = tests)
1968
1969
1971 """Assemble list of all selected cells."""
1972
1973 all_selected_cells = []
1974
1975 all_selected_cells += [ cell_coords.Get() for cell_coords in self.GetSelectedCells() ]
1976
1977 fully_selected_rows = self.GetSelectedRows()
1978 all_selected_cells += list (
1979 (row, col)
1980 for row in fully_selected_rows
1981 for col in range(self.GetNumberCols())
1982 )
1983
1984 fully_selected_cols = self.GetSelectedCols()
1985 all_selected_cells += list (
1986 (row, col)
1987 for row in range(self.GetNumberRows())
1988 for col in fully_selected_cols
1989 )
1990
1991 selected_blocks = zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight())
1992 for top_left_corner, bottom_right_corner in selected_blocks:
1993 all_selected_cells += [
1994 (row, col)
1995 for row in range(top_left_corner[0], bottom_right_corner[0] + 1)
1996 for col in range(top_left_corner[1], bottom_right_corner[1] + 1)
1997 ]
1998 return set(all_selected_cells)
1999
2000
2001 - def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
2002 """Select a range of cells according to criteria.
2003
2004 unsigned_only: include only those which are not signed at all yet
2005 accountable_only: include only those for which the current user is responsible
2006 keep_preselections: broaden (rather than replace) the range of selected cells
2007
2008 Combinations are powerful !
2009 """
2010 wx.BeginBusyCursor()
2011 self.BeginBatch()
2012
2013 if not keep_preselections:
2014 self.ClearSelection()
2015
2016 for col_idx in self.__cell_data:
2017 for row_idx in self.__cell_data[col_idx]:
2018
2019
2020 do_not_include = False
2021 for result in self.__cell_data[col_idx][row_idx]:
2022 if unsigned_only:
2023 if result['reviewed']:
2024 do_not_include = True
2025 break
2026 if accountables_only:
2027 if not result['you_are_responsible']:
2028 do_not_include = True
2029 break
2030 if do_not_include:
2031 continue
2032
2033 self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True)
2034
2035 self.EndBatch()
2036 wx.EndBusyCursor()
2037
2038
2040 self.empty_grid()
2041 if self.__patient is None:
2042 return
2043
2044 if self.__show_by_panel:
2045 if self.__panel_to_show is None:
2046 return
2047 tests = self.__panel_to_show.get_test_types_for_results (
2048 self.__patient.ID,
2049 order_by = 'unified_abbrev',
2050 unique_meta_types = True
2051 )
2052 self.__repopulate_grid (
2053 tests4rows = tests,
2054 test_pks2show = [ tt['pk_test_type'] for tt in self.__panel_to_show['test_types'] ]
2055 )
2056 return
2057
2058 emr = self.__patient.emr
2059 tests = emr.get_test_types_for_results(order_by = 'unified_abbrev', unique_meta_types = True)
2060 self.__repopulate_grid(tests4rows = tests)
2061
2062
2064
2065 if len(tests4rows) == 0:
2066 return
2067
2068 emr = self.__patient.emr
2069
2070 self.__row_label_data = tests4rows
2071 row_labels = [ '%s%s' % (
2072 gmTools.bool2subst(test_type['is_fake_meta_type'], '', gmTools.u_sum, ''),
2073 test_type['unified_abbrev']
2074 ) for test_type in self.__row_label_data
2075 ]
2076
2077 self.__col_label_data = [ d['clin_when_day'] for d in emr.get_dates_for_results (
2078 tests = test_pks2show,
2079 reverse_chronological = True
2080 )]
2081 col_labels = [ gmDateTime.pydt_strftime(date, self.__date_format, accuracy = gmDateTime.acc_days) for date in self.__col_label_data ]
2082
2083 results = emr.get_test_results_by_date (
2084 tests = test_pks2show,
2085 reverse_chronological = True
2086 )
2087
2088 self.BeginBatch()
2089
2090
2091 self.AppendRows(numRows = len(row_labels))
2092 for row_idx in range(len(row_labels)):
2093 self.SetRowLabelValue(row_idx, row_labels[row_idx])
2094
2095
2096 self.AppendCols(numCols = len(col_labels))
2097 for col_idx in range(len(col_labels)):
2098 self.SetColLabelValue(col_idx, col_labels[col_idx])
2099
2100
2101 for result in results:
2102 row_idx = row_labels.index('%s%s' % (
2103 gmTools.bool2subst(result['is_fake_meta_type'], '', gmTools.u_sum, ''),
2104 result['unified_abbrev']
2105 ))
2106 col_idx = col_labels.index(gmDateTime.pydt_strftime(result['clin_when'], self.__date_format, accuracy = gmDateTime.acc_days))
2107
2108 try:
2109 self.__cell_data[col_idx]
2110 except KeyError:
2111 self.__cell_data[col_idx] = {}
2112
2113
2114 if row_idx in self.__cell_data[col_idx]:
2115 self.__cell_data[col_idx][row_idx].append(result)
2116 self.__cell_data[col_idx][row_idx].sort(key = lambda x: x['clin_when'], reverse = True)
2117 else:
2118 self.__cell_data[col_idx][row_idx] = [result]
2119
2120
2121 vals2display = []
2122 cell_has_out_of_bounds_value = False
2123 for sub_result in self.__cell_data[col_idx][row_idx]:
2124
2125 if sub_result.is_considered_abnormal:
2126 cell_has_out_of_bounds_value = True
2127
2128 abnormality_indicator = sub_result.formatted_abnormality_indicator
2129 if abnormality_indicator is None:
2130 abnormality_indicator = ''
2131 if abnormality_indicator != '':
2132 abnormality_indicator = ' (%s)' % abnormality_indicator[:3]
2133
2134 missing_review = False
2135
2136
2137 if not sub_result['reviewed']:
2138 missing_review = True
2139
2140 else:
2141
2142 if sub_result['you_are_responsible'] and not sub_result['review_by_you']:
2143 missing_review = True
2144
2145 needs_superscript = False
2146
2147
2148 if sub_result.is_long_text:
2149 lines = gmTools.strip_empty_lines (
2150 text = sub_result['unified_val'],
2151 eol = '\n',
2152 return_list = True
2153 )
2154 needs_superscript = True
2155 tmp = lines[0][:7]
2156 else:
2157 val = gmTools.strip_empty_lines (
2158 text = sub_result['unified_val'],
2159 eol = '\n',
2160 return_list = False
2161 ).replace('\n', '//')
2162 if len(val) > 8:
2163 needs_superscript = True
2164 tmp = val[:7]
2165 else:
2166 tmp = '%.8s' % val[:8]
2167
2168
2169 tmp = '%s%.6s' % (tmp, abnormality_indicator)
2170
2171
2172 has_sub_result_comment = gmTools.coalesce (
2173 gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']),
2174 ''
2175 ).strip() != ''
2176 if has_sub_result_comment:
2177 needs_superscript = True
2178
2179 if needs_superscript:
2180 tmp = '%s%s' % (tmp, gmTools.u_superscript_one)
2181
2182
2183 if missing_review:
2184 tmp = '%s %s' % (tmp, gmTools.u_writing_hand)
2185 else:
2186 if sub_result['is_clinically_relevant']:
2187 tmp += ' !'
2188
2189
2190 if len(self.__cell_data[col_idx][row_idx]) > 1:
2191 tmp = '%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp)
2192
2193 vals2display.append(tmp)
2194
2195 self.SetCellValue(row_idx, col_idx, '\n'.join(vals2display))
2196 self.SetCellAlignment(row_idx, col_idx, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207 if cell_has_out_of_bounds_value:
2208
2209 self.SetCellBackgroundColour(row_idx, col_idx, 'PALE TURQUOISE')
2210
2211 self.EndBatch()
2212
2213 self.AutoSize()
2214 self.AdjustScrollbars()
2215 self.ForceRefresh()
2216
2217
2218
2219 return
2220
2221
2223 self.BeginBatch()
2224 self.ClearGrid()
2225
2226
2227 if self.GetNumberRows() > 0:
2228 self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
2229 if self.GetNumberCols() > 0:
2230 self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
2231 self.EndBatch()
2232 self.__cell_data = {}
2233 self.__row_label_data = []
2234 self.__col_label_data = []
2235
2236
2254
2255
2281
2282
2283
2284
2286
2287 self.SetMinSize((10, 10))
2288
2289 self.CreateGrid(0, 1)
2290 self.EnableEditing(0)
2291 self.EnableDragGridSize(1)
2292
2293
2294
2295
2296
2297
2298 self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE)
2299
2300 self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE)
2301 font = self.GetLabelFont()
2302 font.SetWeight(wx.FONTWEIGHT_LIGHT)
2303 self.SetLabelFont(font)
2304
2305
2306 dbcfg = gmCfg.cCfgSQL()
2307 url = dbcfg.get2 (
2308 option = 'external.urls.measurements_encyclopedia',
2309 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
2310 bias = 'user',
2311 default = gmPathLab.URL_test_result_information
2312 )
2313
2314 self.__WIN_corner = self.GetGridCornerLabelWindow()
2315
2316 LNK_lab = wxh.HyperlinkCtrl (
2317 self.__WIN_corner,
2318 -1,
2319 label = _('Tests'),
2320 style = wxh.HL_DEFAULT_STYLE
2321 )
2322 LNK_lab.SetURL(url)
2323 LNK_lab.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND))
2324 LNK_lab.SetToolTip(_(
2325 'Navigate to an encyclopedia of measurements\n'
2326 'and test methods on the web.\n'
2327 '\n'
2328 ' <%s>'
2329 ) % url)
2330
2331 SZR_inner = wx.BoxSizer(wx.HORIZONTAL)
2332 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)
2333 SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0)
2334 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)
2335
2336 SZR_corner = wx.BoxSizer(wx.VERTICAL)
2337 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)
2338 SZR_corner.Add(SZR_inner, 0, wx.EXPAND)
2339 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)
2340
2341 self.__WIN_corner.SetSizer(SZR_corner)
2342 SZR_corner.Fit(self.__WIN_corner)
2343
2344
2346 self.__WIN_corner.Layout()
2347
2348
2349 - def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
2350 """List of <cells> must be in row / col order."""
2351 data = []
2352 for row, col in cells:
2353 try:
2354
2355 data_list = self.__cell_data[col][row]
2356 except KeyError:
2357 continue
2358
2359 if len(data_list) == 1:
2360 data.append(data_list[0])
2361 continue
2362
2363 if exclude_multi_cells:
2364 gmDispatcher.send(signal = 'statustext', msg = _('Excluding multi-result field from further processing.'))
2365 continue
2366
2367 if auto_include_multi_cells:
2368 data.extend(data_list)
2369 continue
2370
2371 data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list)
2372 if data_to_include is None:
2373 continue
2374 data.extend(data_to_include)
2375
2376 return data
2377
2378
2380 data = gmListWidgets.get_choices_from_list (
2381 parent = self,
2382 msg = _(
2383 'Your selection includes a field with multiple results.\n'
2384 '\n'
2385 'Please select the individual results you want to work on:'
2386 ),
2387 caption = _('Selecting test results'),
2388 choices = [ [d['clin_when'], '%s: %s' % (d['abbrev_tt'], d['name_tt']), d['unified_val']] for d in cell_data ],
2389 columns = [ _('Date / Time'), _('Test'), _('Result') ],
2390 data = cell_data,
2391 single_selection = single_selection
2392 )
2393 return data
2394
2395
2396
2397
2399
2400 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
2401 self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels)
2402
2403
2404
2405 self.Bind(wx.EVT_SIZE, self.__resize_corner_window)
2406
2407
2408 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
2409
2410
2412 col = evt.GetCol()
2413 row = evt.GetRow()
2414
2415 try:
2416 self.__cell_data[col][row]
2417 except KeyError:
2418 presets = {}
2419 col_date = self.__col_label_data[col]
2420 presets['clin_when'] = {'data': col_date}
2421 test_type = self.__row_label_data[row]
2422 if test_type['pk_meta_test_type'] is not None:
2423 temporally_closest_result_of_row_type = test_type.meta_test_type.get_temporally_closest_result(col_date, self.__patient.ID)
2424 if temporally_closest_result_of_row_type is not None:
2425
2426
2427 presets['pk_test_type'] = {'data': temporally_closest_result_of_row_type['pk_test_type']}
2428
2429
2430
2431 same_day_results = gmPathLab.get_results_for_day (
2432 timestamp = col_date,
2433 patient = self.__patient.ID,
2434 order_by = None
2435 )
2436 if len(same_day_results) > 0:
2437
2438
2439 presets['pk_episode'] = {'data': same_day_results[0]['pk_episode']}
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449 edit_measurement (
2450 parent = self,
2451 measurement = None,
2452 single_entry = True,
2453 presets = presets
2454 )
2455 return
2456
2457 if len(self.__cell_data[col][row]) > 1:
2458 data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True)
2459 else:
2460 data = self.__cell_data[col][row][0]
2461
2462 if data is None:
2463 return
2464
2465 edit_measurement(parent = self, measurement = data, single_entry = True)
2466
2467
2468
2469
2470
2471
2472
2473
2475
2476
2477
2478 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2479
2480 row = self.YToRow(y)
2481
2482 if self.__prev_label_row == row:
2483 return
2484
2485 self.__prev_label_row == row
2486
2487 evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row))
2488
2489
2490
2491
2492
2493
2494
2495
2497 """Calculate where the mouse is and set the tooltip dynamically."""
2498
2499
2500
2501 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515 row, col = self.XYToCell(x, y)
2516
2517 if (row == self.__prev_row) and (col == self.__prev_col):
2518 return
2519
2520 self.__prev_row = row
2521 self.__prev_col = col
2522
2523 evt.GetEventObject().SetToolTip(self.get_cell_tooltip(col=col, row=row))
2524
2525
2526
2527
2529 return self.__patient
2530
2534
2535 patient = property(_get_patient, _set_patient)
2536
2540
2541 panel_to_show = property(lambda x:x, _set_panel_to_show)
2542
2546
2547 show_by_panel = property(lambda x:x, _set_show_by_panel)
2548
2549
2550
2551
2552 from Gnumed.wxGladeWidgets import wxgMeasurementsPnl
2553
2554 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
2555 """Panel holding a grid with lab data. Used as notebook page."""
2556
2564
2565
2566
2568 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
2569 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
2570 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._schedule_data_reget)
2571 gmDispatcher.connect(signal = 'clin.reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
2572
2574 self._schedule_data_reget()
2575
2577 self._GRID_results_all.patient = None
2578 self._GRID_results_battery.patient = None
2579
2582
2586
2590
2593
2599
2602
2622
2625
2628
2631
2633 wx.CallAfter(self.__on_panel_selected, panel=panel)
2634
2636 if panel is None:
2637 self._TCTRL_panel_comment.SetValue('')
2638 self._GRID_results_battery.panel_to_show = None
2639
2640 self._PNL_results_battery_grid.Hide()
2641 else:
2642 pnl = self._PRW_panel.GetData(as_instance = True)
2643 self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
2644 pnl['comment'],
2645 ''
2646 ))
2647 self._GRID_results_battery.panel_to_show = pnl
2648
2649 self._PNL_results_battery_grid.Show()
2650 self._GRID_results_battery.Fit()
2651 self._GRID_results_all.Fit()
2652 self.Layout()
2653
2655 wx.CallAfter(self.__on_panel_selection_modified)
2656
2658 self._TCTRL_panel_comment.SetValue('')
2659 if self._PRW_panel.GetValue().strip() == '':
2660 self._GRID_results_battery.panel_to_show = None
2661
2662 self._PNL_results_battery_grid.Hide()
2663 self.Layout()
2664
2665
2666
2668 self.SetMinSize((10, 10))
2669
2670 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
2671
2672 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
2673 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
2674
2675 item = self.__action_button_popup.Append(-1, _('Plot'))
2676 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
2677
2678 item = self.__action_button_popup.Append(-1, _('Export to &file'))
2679 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item)
2680 self.__action_button_popup.Enable(id = menu_id, enable = False)
2681
2682 item = self.__action_button_popup.Append(-1, _('Export to &clipboard'))
2683 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item)
2684 self.__action_button_popup.Enable(id = menu_id, enable = False)
2685
2686 item = self.__action_button_popup.Append(-1, _('&Delete'))
2687 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
2688
2689
2690
2691
2692 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
2693 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
2694
2695 self._GRID_results_battery.show_by_panel = True
2696 self._GRID_results_battery.panel_to_show = None
2697
2698 self._PNL_results_battery_grid.Hide()
2699 self._BTN_display_mode.SetLabel(_('All: by &Day'))
2700
2701 self._PNL_results_all_grid.Show()
2702 self._PNL_results_all_listed.Hide()
2703 self.Layout()
2704
2705 self._PRW_panel.SetFocus()
2706
2707
2708
2710 pat = gmPerson.gmCurrentPatient()
2711 if pat.connected:
2712 self._GRID_results_battery.patient = pat
2713 if self.__display_mode == 'grid':
2714 self._GRID_results_all.patient = pat
2715 self._PNL_results_all_listed.patient = None
2716 else:
2717 self._GRID_results_all.patient = None
2718 self._PNL_results_all_listed.patient = pat
2719 else:
2720 self._GRID_results_battery.patient = None
2721 self._GRID_results_all.patient = None
2722 self._PNL_results_all_listed.patient = None
2723 return True
2724
2725
2726
2727
2729
2730 if tests is None:
2731 return True
2732
2733 if len(tests) == 0:
2734 return True
2735
2736 if parent is None:
2737 parent = wx.GetApp().GetTopWindow()
2738
2739 if len(tests) > 10:
2740 test_count = len(tests)
2741 tests2show = None
2742 else:
2743 test_count = None
2744 tests2show = tests
2745 if len(tests) == 0:
2746 return True
2747
2748 dlg = cMeasurementsReviewDlg(parent, -1, tests = tests, test_count = test_count)
2749 decision = dlg.ShowModal()
2750 if decision != wx.ID_APPLY:
2751 return True
2752
2753 wx.BeginBusyCursor()
2754 if dlg._RBTN_confirm_abnormal.GetValue():
2755 abnormal = None
2756 elif dlg._RBTN_results_normal.GetValue():
2757 abnormal = False
2758 else:
2759 abnormal = True
2760
2761 if dlg._RBTN_confirm_relevance.GetValue():
2762 relevant = None
2763 elif dlg._RBTN_results_not_relevant.GetValue():
2764 relevant = False
2765 else:
2766 relevant = True
2767
2768 comment = None
2769 if len(tests) == 1:
2770 comment = dlg._TCTRL_comment.GetValue()
2771
2772 make_responsible = dlg._CHBOX_responsible.IsChecked()
2773 dlg.DestroyLater()
2774
2775 for test in tests:
2776 test.set_review (
2777 technically_abnormal = abnormal,
2778 clinically_relevant = relevant,
2779 comment = comment,
2780 make_me_responsible = make_responsible
2781 )
2782 wx.EndBusyCursor()
2783
2784 return True
2785
2786
2787 from Gnumed.wxGladeWidgets import wxgMeasurementsReviewDlg
2788
2790
2792
2793 try:
2794 tests = kwargs['tests']
2795 del kwargs['tests']
2796 test_count = len(tests)
2797 try: del kwargs['test_count']
2798 except KeyError: pass
2799 except KeyError:
2800 tests = None
2801 test_count = kwargs['test_count']
2802 del kwargs['test_count']
2803
2804 wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs)
2805
2806 if tests is None:
2807 msg = _('%s results selected. Too many to list individually.') % test_count
2808 else:
2809 msg = '\n'.join (
2810 [ '%s: %s %s (%s)' % (
2811 t['unified_abbrev'],
2812 t['unified_val'],
2813 t['val_unit'],
2814 gmDateTime.pydt_strftime(t['clin_when'], '%Y %b %d')
2815 ) for t in tests
2816 ]
2817 )
2818
2819 self._LBL_tests.SetLabel(msg)
2820
2821 if test_count == 1:
2822 self._TCTRL_comment.Enable(True)
2823 self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], ''))
2824 if tests[0]['you_are_responsible']:
2825 self._CHBOX_responsible.Enable(False)
2826
2827 self.Fit()
2828
2829
2830
2836
2837
2838 from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl
2839
2840 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
2841 """This edit area saves *new* measurements into the active patient only."""
2842
2859
2860
2861
2862
2864 self._TCTRL_result.SetFocus()
2865 try:
2866 self._PRW_test.SetData(data = fields['pk_test_type']['data'])
2867 except KeyError:
2868 self._PRW_test.SetFocus()
2869 try:
2870 self._DPRW_evaluated.SetData(data = fields['clin_when']['data'])
2871 except KeyError:
2872 pass
2873 try:
2874 self._PRW_problem.SetData(data = fields['pk_episode']['data'])
2875 except KeyError:
2876 pass
2877 try:
2878 self._PRW_units.SetText(fields['val_unit']['data'], fields['val_unit']['data'], True)
2879 except KeyError:
2880 pass
2881 try:
2882 self._TCTRL_normal_min.SetValue(fields['val_normal_min']['data'])
2883 except KeyError:
2884 pass
2885 try:
2886 self._TCTRL_normal_max.SetValue(fields['val_normal_max']['data'])
2887 except KeyError:
2888 pass
2889 try:
2890 self._TCTRL_normal_range.SetValue(fields['val_normal_range']['data'])
2891 except KeyError:
2892 pass
2893 try:
2894 self._TCTRL_target_min.SetValue(fields['val_target_min']['data'])
2895 except KeyError:
2896 pass
2897 try:
2898 self._TCTRL_target_max.SetValue(fields['val_target_max']['data'])
2899 except KeyError:
2900 pass
2901 try:
2902 self._TCTRL_target_range.SetValue(fields['val_target_range']['data'])
2903 except KeyError:
2904 pass
2905
2906
2938
2940 self._PRW_test.SetData(data = self.data['pk_test_type'])
2941 self.__refresh_loinc_info()
2942 self.__refresh_previous_value()
2943 self.__update_units_context()
2944 self._TCTRL_result.SetValue(self.data['unified_val'])
2945 self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True)
2946 self._PRW_abnormality_indicator.SetText (
2947 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2948 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2949 True
2950 )
2951 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2952 self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], ''))
2953 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2954 self._PRW_problem.SetData(self.data['pk_episode'])
2955 self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], ''))
2956 self._CHBOX_review.SetValue(False)
2957 self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False))
2958 self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False))
2959 self._CHBOX_abnormal.Enable(False)
2960 self._CHBOX_relevant.Enable(False)
2961 self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], ''))
2962 self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(self.data['val_normal_min'], '')))
2963 self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(self.data['val_normal_max'], '')))
2964 self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], ''))
2965 self._TCTRL_target_min.SetValue(str(gmTools.coalesce(self.data['val_target_min'], '')))
2966 self._TCTRL_target_max.SetValue(str(gmTools.coalesce(self.data['val_target_max'], '')))
2967 self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], ''))
2968 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], ''))
2969
2970 self._TCTRL_result.SetFocus()
2971
2973 self._PRW_test.SetText('', None, True)
2974 self.__refresh_loinc_info()
2975 self.__refresh_previous_value()
2976 self.__update_units_context()
2977 self._TCTRL_result.SetValue('')
2978 self._PRW_units.SetText('', None, True)
2979 self._PRW_abnormality_indicator.SetText('', None, True)
2980 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2981 self._TCTRL_note_test_org.SetValue('')
2982 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2983 self._PRW_problem.SetData(self.data['pk_episode'])
2984 self._TCTRL_narrative.SetValue('')
2985 self._CHBOX_review.SetValue(False)
2986 self._CHBOX_abnormal.SetValue(False)
2987 self._CHBOX_relevant.SetValue(False)
2988 self._CHBOX_abnormal.Enable(False)
2989 self._CHBOX_relevant.Enable(False)
2990 self._TCTRL_review_comment.SetValue('')
2991 self._TCTRL_normal_min.SetValue('')
2992 self._TCTRL_normal_max.SetValue('')
2993 self._TCTRL_normal_range.SetValue('')
2994 self._TCTRL_target_min.SetValue('')
2995 self._TCTRL_target_max.SetValue('')
2996 self._TCTRL_target_range.SetValue('')
2997 self._TCTRL_norm_ref_group.SetValue('')
2998
2999 self._PRW_test.SetFocus()
3000
3002
3003 validity = True
3004
3005 if not self._DPRW_evaluated.is_valid_timestamp():
3006 self._DPRW_evaluated.display_as_valid(False)
3007 validity = False
3008 else:
3009 self._DPRW_evaluated.display_as_valid(True)
3010
3011 val = self._TCTRL_result.GetValue().strip()
3012 if val == '':
3013 validity = False
3014 self.display_ctrl_as_valid(self._TCTRL_result, False)
3015 else:
3016 self.display_ctrl_as_valid(self._TCTRL_result, True)
3017 numeric, val = gmTools.input2decimal(val)
3018 if numeric:
3019 if self._PRW_units.GetValue().strip() == '':
3020 self._PRW_units.display_as_valid(False)
3021 validity = False
3022 else:
3023 self._PRW_units.display_as_valid(True)
3024 else:
3025 self._PRW_units.display_as_valid(True)
3026
3027 if self._PRW_problem.GetValue().strip() == '':
3028 self._PRW_problem.display_as_valid(False)
3029 validity = False
3030 else:
3031 self._PRW_problem.display_as_valid(True)
3032
3033 if self._PRW_test.GetValue().strip() == '':
3034 self._PRW_test.display_as_valid(False)
3035 validity = False
3036 else:
3037 self._PRW_test.display_as_valid(True)
3038
3039 if self._PRW_intended_reviewer.GetData() is None:
3040 self._PRW_intended_reviewer.display_as_valid(False)
3041 validity = False
3042 else:
3043 self._PRW_intended_reviewer.display_as_valid(True)
3044
3045 ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max]
3046 for widget in ctrls:
3047 val = widget.GetValue().strip()
3048 if val == '':
3049 continue
3050 try:
3051 decimal.Decimal(val.replace(',', '.', 1))
3052 self.display_ctrl_as_valid(widget, True)
3053 except Exception:
3054 validity = False
3055 self.display_ctrl_as_valid(widget, False)
3056
3057 if validity is False:
3058 self.StatusText = _('Cannot save result. Invalid or missing essential input.')
3059
3060 return validity
3061
3063
3064 emr = gmPerson.gmCurrentPatient().emr
3065
3066 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
3067 if success:
3068 v_num = result
3069 v_al = None
3070 else:
3071 v_al = self._TCTRL_result.GetValue().strip()
3072 v_num = None
3073
3074 pk_type = self._PRW_test.GetData()
3075 if pk_type is None:
3076 abbrev = self._PRW_test.GetValue().strip()
3077 name = self._PRW_test.GetValue().strip()
3078 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3079 lab = manage_measurement_orgs (
3080 parent = self,
3081 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
3082 )
3083 if lab is not None:
3084 lab = lab['pk_test_org']
3085 tt = gmPathLab.create_measurement_type (
3086 lab = lab,
3087 abbrev = abbrev,
3088 name = name,
3089 unit = unit
3090 )
3091 pk_type = tt['pk_test_type']
3092
3093 tr = emr.add_test_result (
3094 episode = self._PRW_problem.GetData(can_create=True, is_open=False),
3095 type = pk_type,
3096 intended_reviewer = self._PRW_intended_reviewer.GetData(),
3097 val_num = v_num,
3098 val_alpha = v_al,
3099 unit = self._PRW_units.GetValue()
3100 )
3101
3102 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
3103
3104 ctrls = [
3105 ('abnormality_indicator', self._PRW_abnormality_indicator),
3106 ('note_test_org', self._TCTRL_note_test_org),
3107 ('comment', self._TCTRL_narrative),
3108 ('val_normal_range', self._TCTRL_normal_range),
3109 ('val_target_range', self._TCTRL_target_range),
3110 ('norm_ref_group', self._TCTRL_norm_ref_group)
3111 ]
3112 for field, widget in ctrls:
3113 tr[field] = widget.GetValue().strip()
3114
3115 ctrls = [
3116 ('val_normal_min', self._TCTRL_normal_min),
3117 ('val_normal_max', self._TCTRL_normal_max),
3118 ('val_target_min', self._TCTRL_target_min),
3119 ('val_target_max', self._TCTRL_target_max)
3120 ]
3121 for field, widget in ctrls:
3122 val = widget.GetValue().strip()
3123 if val == '':
3124 tr[field] = None
3125 else:
3126 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
3127
3128 tr.save_payload()
3129
3130 if self._CHBOX_review.GetValue() is True:
3131 tr.set_review (
3132 technically_abnormal = self._CHBOX_abnormal.GetValue(),
3133 clinically_relevant = self._CHBOX_relevant.GetValue(),
3134 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
3135 make_me_responsible = False
3136 )
3137
3138 self.data = tr
3139
3140
3141
3142
3143
3144
3145
3146
3147 return True
3148
3150
3151 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
3152 if success:
3153 v_num = result
3154 v_al = None
3155 else:
3156 v_num = None
3157 v_al = self._TCTRL_result.GetValue().strip()
3158
3159 pk_type = self._PRW_test.GetData()
3160 if pk_type is None:
3161 abbrev = self._PRW_test.GetValue().strip()
3162 name = self._PRW_test.GetValue().strip()
3163 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3164 lab = manage_measurement_orgs (
3165 parent = self,
3166 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
3167 )
3168 if lab is not None:
3169 lab = lab['pk_test_org']
3170 tt = gmPathLab.create_measurement_type (
3171 lab = None,
3172 abbrev = abbrev,
3173 name = name,
3174 unit = unit
3175 )
3176 pk_type = tt['pk_test_type']
3177
3178 tr = self.data
3179
3180 tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False)
3181 tr['pk_test_type'] = pk_type
3182 tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData()
3183 tr['val_num'] = v_num
3184 tr['val_alpha'] = v_al
3185 tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3186 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
3187
3188 ctrls = [
3189 ('abnormality_indicator', self._PRW_abnormality_indicator),
3190 ('note_test_org', self._TCTRL_note_test_org),
3191 ('comment', self._TCTRL_narrative),
3192 ('val_normal_range', self._TCTRL_normal_range),
3193 ('val_target_range', self._TCTRL_target_range),
3194 ('norm_ref_group', self._TCTRL_norm_ref_group)
3195 ]
3196 for field, widget in ctrls:
3197 tr[field] = widget.GetValue().strip()
3198
3199 ctrls = [
3200 ('val_normal_min', self._TCTRL_normal_min),
3201 ('val_normal_max', self._TCTRL_normal_max),
3202 ('val_target_min', self._TCTRL_target_min),
3203 ('val_target_max', self._TCTRL_target_max)
3204 ]
3205 for field, widget in ctrls:
3206 val = widget.GetValue().strip()
3207 if val == '':
3208 tr[field] = None
3209 else:
3210 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
3211
3212 tr.save_payload()
3213
3214 if self._CHBOX_review.GetValue() is True:
3215 tr.set_review (
3216 technically_abnormal = self._CHBOX_abnormal.GetValue(),
3217 clinically_relevant = self._CHBOX_relevant.GetValue(),
3218 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
3219 make_me_responsible = False
3220 )
3221
3222
3223
3224
3225
3226
3227
3228
3229 return True
3230
3231
3232
3237
3239 self.__refresh_loinc_info()
3240 self.__refresh_previous_value()
3241 self.__update_units_context()
3242
3243 self.__update_normal_range()
3244 self.__update_clinical_range()
3245
3247
3248 self.__update_normal_range()
3249 self.__update_clinical_range()
3250
3252
3253 if not self._CHBOX_review.GetValue():
3254 self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != '')
3255
3260
3276
3280
3281
3282
3284
3285 if self._PRW_test.GetData() is None:
3286 self._PRW_units.unset_context(context = 'pk_type')
3287 self._PRW_units.unset_context(context = 'loinc')
3288 if self._PRW_test.GetValue().strip() == '':
3289 self._PRW_units.unset_context(context = 'test_name')
3290 else:
3291 self._PRW_units.set_context(context = 'test_name', val = self._PRW_test.GetValue().strip())
3292 return
3293
3294 tt = self._PRW_test.GetData(as_instance = True)
3295
3296 self._PRW_units.set_context(context = 'pk_type', val = tt['pk_test_type'])
3297 self._PRW_units.set_context(context = 'test_name', val = tt['name'])
3298
3299 if tt['loinc'] is not None:
3300 self._PRW_units.set_context(context = 'loinc', val = tt['loinc'])
3301
3302
3303 if self._PRW_units.GetValue().strip() == '':
3304 clin_when = self._DPRW_evaluated.GetData()
3305 if clin_when is None:
3306 unit = tt.temporally_closest_unit
3307 else:
3308 clin_when = clin_when.get_pydt()
3309 unit = tt.get_temporally_closest_unit(timestamp = clin_when)
3310 if unit is None:
3311 self._PRW_units.SetText('', unit, True)
3312 else:
3313 self._PRW_units.SetText(unit, unit, True)
3314
3315
3336
3337
3358
3359
3361
3362 self._TCTRL_loinc.SetValue('')
3363
3364 if self._PRW_test.GetData() is None:
3365 return
3366
3367 tt = self._PRW_test.GetData(as_instance = True)
3368
3369 if tt['loinc'] is None:
3370 return
3371
3372 info = gmLOINC.loinc2term(loinc = tt['loinc'])
3373 if len(info) == 0:
3374 self._TCTRL_loinc.SetValue('')
3375 return
3376
3377 self._TCTRL_loinc.SetValue('%s: %s' % (tt['loinc'], info[0]))
3378
3379
3381 self._TCTRL_previous_value.SetValue('')
3382
3383
3384 if self.data is not None:
3385 return
3386
3387 if self._PRW_test.GetData() is None:
3388 return
3389
3390 tt = self._PRW_test.GetData(as_instance = True)
3391 most_recent_results = tt.get_most_recent_results (
3392 max_no_of_results = 1,
3393 patient = gmPerson.gmCurrentPatient().ID
3394 )
3395 if len(most_recent_results) == 0:
3396 return
3397
3398 most_recent = most_recent_results[0]
3399 self._TCTRL_previous_value.SetValue(_('%s ago: %s%s%s - %s%s') % (
3400 gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - most_recent['clin_when']),
3401 most_recent['unified_val'],
3402 most_recent['val_unit'],
3403 gmTools.coalesce(most_recent['abnormality_indicator'], '', ' (%s)'),
3404 most_recent['abbrev_tt'],
3405 gmTools.coalesce(most_recent.formatted_range, '', ' [%s]')
3406 ))
3407 self._TCTRL_previous_value.SetToolTip(most_recent.format (
3408 with_review = True,
3409 with_evaluation = False,
3410 with_ranges = True,
3411 with_episode = True,
3412 with_type_details=True
3413 ))
3414
3415
3416
3417
3419
3420 if parent is None:
3421 parent = wx.GetApp().GetTopWindow()
3422
3423 if msg is None:
3424 msg = _('Pick the relevant measurement types.')
3425
3426 if right_column is None:
3427 right_columns = [_('Picked')]
3428 else:
3429 right_columns = [right_column]
3430
3431 picker = gmListWidgets.cItemPickerDlg(parent, -1, msg = msg)
3432 picker.set_columns(columns = [_('Known measurement types')], columns_right = right_columns)
3433 types = gmPathLab.get_measurement_types(order_by = 'unified_abbrev')
3434 picker.set_choices (
3435 choices = [
3436 '%s: %s%s' % (
3437 t['unified_abbrev'],
3438 t['unified_name'],
3439 gmTools.coalesce(t['name_org'], '', ' (%s)')
3440 )
3441 for t in types
3442 ],
3443 data = types
3444 )
3445 if picks is not None:
3446 picker.set_picks (
3447 picks = [
3448 '%s: %s%s' % (
3449 p['unified_abbrev'],
3450 p['unified_name'],
3451 gmTools.coalesce(p['name_org'], '', ' (%s)')
3452 )
3453 for p in picks
3454 ],
3455 data = picks
3456 )
3457 result = picker.ShowModal()
3458
3459 if result == wx.ID_CANCEL:
3460 picker.DestroyLater()
3461 return None
3462
3463 picks = picker.picks
3464 picker.DestroyLater()
3465 return picks
3466
3467
3490
3491
3492 def delete(measurement_type):
3493 if measurement_type.in_use:
3494 gmDispatcher.send (
3495 signal = 'statustext',
3496 beep = True,
3497 msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev'])
3498 )
3499 return False
3500 gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type'])
3501 return True
3502
3503
3504 def get_tooltip(test_type):
3505 return test_type.format()
3506
3507
3508 def manage_aggregates(test_type):
3509 manage_meta_test_types(parent = parent)
3510 return False
3511
3512
3513 def manage_panels_of_type(test_type):
3514 if test_type['loinc'] is None:
3515 return False
3516 all_panels = gmPathLab.get_test_panels(order_by = 'description')
3517 curr_panels = test_type.test_panels
3518 if curr_panels is None:
3519 curr_panels = []
3520 panel_candidates = [ p for p in all_panels if p['pk_test_panel'] not in [
3521 c_pnl['pk_test_panel'] for c_pnl in curr_panels
3522 ] ]
3523 picker = gmListWidgets.cItemPickerDlg(parent, -1, title = 'Panels with [%s]' % test_type['abbrev'])
3524 picker.set_columns(['Panels available'], ['Panels [%s] is to be on' % test_type['abbrev']])
3525 picker.set_choices (
3526 choices = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in panel_candidates ],
3527 data = panel_candidates
3528 )
3529 picker.set_picks (
3530 picks = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in curr_panels ],
3531 data = curr_panels
3532 )
3533 exit_type = picker.ShowModal()
3534 if exit_type == wx.ID_CANCEL:
3535 return False
3536
3537
3538 panels2add = [ p for p in picker.picks if p['pk_test_panel'] not in [
3539 c_pnl['pk_test_panel'] for c_pnl in curr_panels
3540 ] ]
3541
3542 panels2remove = [ p for p in curr_panels if p['pk_test_panel'] not in [
3543 picked_pnl['pk_test_panel'] for picked_pnl in picker.picks
3544 ] ]
3545 for new_panel in panels2add:
3546 new_panel.add_loinc(test_type['loinc'])
3547 for stale_panel in panels2remove:
3548 stale_panel.remove_loinc(test_type['loinc'])
3549
3550 return True
3551
3552
3553 def refresh(lctrl):
3554 mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev')
3555 items = [ [
3556 m['abbrev'],
3557 m['name'],
3558 gmTools.coalesce(m['reference_unit'], ''),
3559 gmTools.coalesce(m['loinc'], ''),
3560 gmTools.coalesce(m['comment_type'], ''),
3561 gmTools.coalesce(m['name_org'], '?'),
3562 gmTools.coalesce(m['comment_org'], ''),
3563 m['pk_test_type']
3564 ] for m in mtypes ]
3565 lctrl.set_string_items(items)
3566 lctrl.set_data(mtypes)
3567
3568
3569 gmListWidgets.get_choices_from_list (
3570 parent = parent,
3571 caption = _('Measurement types.'),
3572 columns = [ _('Abbrev'), _('Name'), _('Unit'), _('LOINC'), _('Comment'), _('Org'), _('Comment'), '#' ],
3573 single_selection = True,
3574 refresh_callback = refresh,
3575 edit_callback = edit,
3576 new_callback = edit,
3577 delete_callback = delete,
3578 list_tooltip_callback = get_tooltip,
3579 left_extra_button = (_('%s &Aggregate') % gmTools.u_sum, _('Manage aggregations (%s) of tests into groups.') % gmTools.u_sum, manage_aggregates),
3580 middle_extra_button = (_('Select panels'), _('Select panels the focussed test type is to belong to.'), manage_panels_of_type)
3581 )
3582
3583
3585
3587
3588 query = """
3589 SELECT DISTINCT ON (field_label)
3590 pk_test_type AS data,
3591 name
3592 || ' ('
3593 || coalesce (
3594 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
3595 '%(in_house)s'
3596 )
3597 || ')'
3598 AS field_label,
3599 name
3600 || ' ('
3601 || abbrev || ', '
3602 || coalesce(abbrev_meta || ': ' || name_meta || ', ', '')
3603 || coalesce (
3604 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
3605 '%(in_house)s'
3606 )
3607 || ')'
3608 AS list_label
3609 FROM
3610 clin.v_test_types c_vtt
3611 WHERE
3612 abbrev_meta %%(fragment_condition)s
3613 OR
3614 name_meta %%(fragment_condition)s
3615 OR
3616 abbrev %%(fragment_condition)s
3617 OR
3618 name %%(fragment_condition)s
3619 ORDER BY field_label
3620 LIMIT 50""" % {'in_house': _('generic / in house lab')}
3621
3622 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3623 mp.setThresholds(1, 2, 4)
3624 mp.word_separators = '[ \t:@]+'
3625 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
3626 self.matcher = mp
3627 self.SetToolTip(_('Select the type of measurement.'))
3628 self.selection_only = False
3629
3630
3636
3637
3639 lab = gmPathLab.cTestOrg(aPK_obj = instance['pk_test_org'])
3640 field_label = '%s (%s @ %s)' % (
3641 instance['name'],
3642 lab['unit'],
3643 lab['organization']
3644 )
3645 return self.SetText(value = field_label, data = instance['pk_test_type'])
3646
3647
3650
3651
3654
3655
3656 from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl
3657
3658 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
3659
3676
3677
3679
3680
3681 query = """
3682 select distinct on (name)
3683 pk,
3684 name
3685 from clin.test_type
3686 where
3687 name %(fragment_condition)s
3688 order by name
3689 limit 50"""
3690 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3691 mp.setThresholds(1, 2, 4)
3692 self._PRW_name.matcher = mp
3693 self._PRW_name.selection_only = False
3694 self._PRW_name.add_callback_on_lose_focus(callback = self._on_name_lost_focus)
3695
3696
3697 query = """
3698 select distinct on (abbrev)
3699 pk,
3700 abbrev
3701 from clin.test_type
3702 where
3703 abbrev %(fragment_condition)s
3704 order by abbrev
3705 limit 50"""
3706 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3707 mp.setThresholds(1, 2, 3)
3708 self._PRW_abbrev.matcher = mp
3709 self._PRW_abbrev.selection_only = False
3710
3711
3712 self._PRW_reference_unit.selection_only = False
3713
3714
3715 mp = gmLOINC.cLOINCMatchProvider()
3716 mp.setThresholds(1, 2, 4)
3717
3718
3719 self._PRW_loinc.matcher = mp
3720 self._PRW_loinc.selection_only = False
3721 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
3722
3723
3725
3726 test = self._PRW_name.GetValue().strip()
3727
3728 if test == '':
3729 self._PRW_reference_unit.unset_context(context = 'test_name')
3730 return
3731
3732 self._PRW_reference_unit.set_context(context = 'test_name', val = test)
3733
3734
3736 loinc = self._PRW_loinc.GetData()
3737
3738 if loinc is None:
3739 self._TCTRL_loinc_info.SetValue('')
3740 self._PRW_reference_unit.unset_context(context = 'loinc')
3741 return
3742
3743 self._PRW_reference_unit.set_context(context = 'loinc', val = loinc)
3744
3745 info = gmLOINC.loinc2term(loinc = loinc)
3746 if len(info) == 0:
3747 self._TCTRL_loinc_info.SetValue('')
3748 return
3749
3750 self._TCTRL_loinc_info.SetValue(info[0])
3751
3752
3753
3754
3756
3757 has_errors = False
3758 for field in [self._PRW_name, self._PRW_abbrev, self._PRW_reference_unit]:
3759 if field.GetValue().strip() in ['', None]:
3760 has_errors = True
3761 field.display_as_valid(valid = False)
3762 else:
3763 field.display_as_valid(valid = True)
3764 field.Refresh()
3765
3766 return (not has_errors)
3767
3768
3798
3837
3838
3840 self._PRW_name.SetText('', None, True)
3841 self._on_name_lost_focus()
3842 self._PRW_abbrev.SetText('', None, True)
3843 self._PRW_reference_unit.SetText('', None, True)
3844 self._PRW_loinc.SetText('', None, True)
3845 self._on_loinc_lost_focus()
3846 self._TCTRL_comment_type.SetValue('')
3847 self._PRW_test_org.SetText('', None, True)
3848 self._PRW_meta_type.SetText('', None, True)
3849
3850 self._PRW_name.SetFocus()
3851
3853 self._PRW_name.SetText(self.data['name'], self.data['name'], True)
3854 self._on_name_lost_focus()
3855 self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True)
3856 self._PRW_reference_unit.SetText (
3857 gmTools.coalesce(self.data['reference_unit'], ''),
3858 self.data['reference_unit'],
3859 True
3860 )
3861 self._PRW_loinc.SetText (
3862 gmTools.coalesce(self.data['loinc'], ''),
3863 self.data['loinc'],
3864 True
3865 )
3866 self._on_loinc_lost_focus()
3867 self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], ''))
3868 self._PRW_test_org.SetText (
3869 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
3870 self.data['pk_test_org'],
3871 True
3872 )
3873 if self.data['pk_meta_test_type'] is None:
3874 self._PRW_meta_type.SetText('', None, True)
3875 else:
3876 self._PRW_meta_type.SetText('%s: %s' % (self.data['abbrev_meta'], self.data['name_meta']), self.data['pk_meta_test_type'], True)
3877
3878 self._PRW_name.SetFocus()
3879
3881 self._refresh_as_new()
3882 self._PRW_test_org.SetText (
3883 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
3884 self.data['pk_test_org'],
3885 True
3886 )
3887 self._PRW_name.SetFocus()
3888
3889
3890 _SQL_units_from_test_results = """
3891 -- via clin.v_test_results.pk_type (for types already used in results)
3892 SELECT
3893 val_unit AS data,
3894 val_unit AS field_label,
3895 val_unit || ' (' || name_tt || ')' AS list_label,
3896 1 AS rank
3897 FROM
3898 clin.v_test_results
3899 WHERE
3900 (
3901 val_unit %(fragment_condition)s
3902 OR
3903 reference_unit %(fragment_condition)s
3904 )
3905 %(ctxt_type_pk)s
3906 %(ctxt_test_name)s
3907 """
3908
3909 _SQL_units_from_test_types = """
3910 -- via clin.test_type (for types not yet used in results)
3911 SELECT
3912 reference_unit AS data,
3913 reference_unit AS field_label,
3914 reference_unit || ' (' || name || ')' AS list_label,
3915 2 AS rank
3916 FROM
3917 clin.test_type
3918 WHERE
3919 reference_unit %(fragment_condition)s
3920 %(ctxt_ctt)s
3921 """
3922
3923 _SQL_units_from_loinc_ipcc = """
3924 -- via ref.loinc.ipcc_units
3925 SELECT
3926 ipcc_units AS data,
3927 ipcc_units AS field_label,
3928 ipcc_units || ' (LOINC.ipcc: ' || term || ')' AS list_label,
3929 3 AS rank
3930 FROM
3931 ref.loinc
3932 WHERE
3933 ipcc_units %(fragment_condition)s
3934 %(ctxt_loinc)s
3935 %(ctxt_loinc_term)s
3936 """
3937
3938 _SQL_units_from_loinc_submitted = """
3939 -- via ref.loinc.submitted_units
3940 SELECT
3941 submitted_units AS data,
3942 submitted_units AS field_label,
3943 submitted_units || ' (LOINC.submitted:' || term || ')' AS list_label,
3944 3 AS rank
3945 FROM
3946 ref.loinc
3947 WHERE
3948 submitted_units %(fragment_condition)s
3949 %(ctxt_loinc)s
3950 %(ctxt_loinc_term)s
3951 """
3952
3953 _SQL_units_from_loinc_example = """
3954 -- via ref.loinc.example_units
3955 SELECT
3956 example_units AS data,
3957 example_units AS field_label,
3958 example_units || ' (LOINC.example: ' || term || ')' AS list_label,
3959 3 AS rank
3960 FROM
3961 ref.loinc
3962 WHERE
3963 example_units %(fragment_condition)s
3964 %(ctxt_loinc)s
3965 %(ctxt_loinc_term)s
3966 """
3967
3968 _SQL_units_from_substance_doses = """
3969 -- via ref.v_substance_doses.unit
3970 SELECT
3971 unit AS data,
3972 unit AS field_label,
3973 unit || ' (' || substance || ')' AS list_label,
3974 2 AS rank
3975 FROM
3976 ref.v_substance_doses
3977 WHERE
3978 unit %(fragment_condition)s
3979 %(ctxt_substance)s
3980 """
3981
3982 _SQL_units_from_substance_doses2 = """
3983 -- via ref.v_substance_doses.dose_unit
3984 SELECT
3985 dose_unit AS data,
3986 dose_unit AS field_label,
3987 dose_unit || ' (' || substance || ')' AS list_label,
3988 2 AS rank
3989 FROM
3990 ref.v_substance_doses
3991 WHERE
3992 dose_unit %(fragment_condition)s
3993 %(ctxt_substance)s
3994 """
3995
3996
3998
4000
4001 query = """
4002 SELECT DISTINCT ON (data)
4003 data,
4004 field_label,
4005 list_label
4006 FROM (
4007
4008 SELECT
4009 data,
4010 field_label,
4011 list_label,
4012 rank
4013 FROM (
4014 (%s) UNION ALL
4015 (%s) UNION ALL
4016 (%s) UNION ALL
4017 (%s) UNION ALL
4018 (%s) UNION ALL
4019 (%s) UNION ALL
4020 (%s)
4021 ) AS all_matching_units
4022 WHERE data IS NOT NULL
4023 ORDER BY rank, list_label
4024
4025 ) AS ranked_matching_units
4026 LIMIT 50""" % (
4027 _SQL_units_from_test_results,
4028 _SQL_units_from_test_types,
4029 _SQL_units_from_loinc_ipcc,
4030 _SQL_units_from_loinc_submitted,
4031 _SQL_units_from_loinc_example,
4032 _SQL_units_from_substance_doses,
4033 _SQL_units_from_substance_doses2
4034 )
4035
4036 ctxt = {
4037 'ctxt_type_pk': {
4038 'where_part': 'AND pk_test_type = %(pk_type)s',
4039 'placeholder': 'pk_type'
4040 },
4041 'ctxt_test_name': {
4042 'where_part': 'AND %(test_name)s IN (name_tt, name_meta, abbrev_meta)',
4043 'placeholder': 'test_name'
4044 },
4045 'ctxt_ctt': {
4046 'where_part': 'AND %(test_name)s IN (name, abbrev)',
4047 'placeholder': 'test_name'
4048 },
4049 'ctxt_loinc': {
4050 'where_part': 'AND code = %(loinc)s',
4051 'placeholder': 'loinc'
4052 },
4053 'ctxt_loinc_term': {
4054 'where_part': 'AND term ~* %(test_name)s',
4055 'placeholder': 'test_name'
4056 },
4057 'ctxt_substance': {
4058 'where_part': 'AND description ~* %(substance)s',
4059 'placeholder': 'substance'
4060 }
4061 }
4062
4063 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = ctxt)
4064 mp.setThresholds(1, 2, 4)
4065
4066 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4067 self.matcher = mp
4068 self.SetToolTip(_('Select the desired unit for the amount or measurement.'))
4069 self.selection_only = False
4070 self.phrase_separators = '[;|]+'
4071
4072
4073
4074
4076
4078
4079 query = """
4080 select distinct abnormality_indicator,
4081 abnormality_indicator, abnormality_indicator
4082 from clin.v_test_results
4083 where
4084 abnormality_indicator %(fragment_condition)s
4085 order by abnormality_indicator
4086 limit 25"""
4087
4088 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4089 mp.setThresholds(1, 1, 2)
4090 mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"'
4091 mp.word_separators = '[ \t&:]+'
4092 gmPhraseWheel.cPhraseWheel.__init__ (
4093 self,
4094 *args,
4095 **kwargs
4096 )
4097 self.matcher = mp
4098 self.SetToolTip(_('Select an indicator for the level of abnormality.'))
4099 self.selection_only = False
4100
4101
4102
4103
4115
4124
4125 def refresh(lctrl):
4126 orgs = gmPathLab.get_test_orgs()
4127 lctrl.set_string_items ([
4128 (o['unit'], o['organization'], gmTools.coalesce(o['test_org_contact'], ''), gmTools.coalesce(o['comment'], ''), o['pk_test_org'])
4129 for o in orgs
4130 ])
4131 lctrl.set_data(orgs)
4132
4133 def delete(test_org):
4134 gmPathLab.delete_test_org(test_org = test_org['pk_test_org'])
4135 return True
4136
4137 if msg is None:
4138 msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n')
4139
4140 return gmListWidgets.get_choices_from_list (
4141 parent = parent,
4142 msg = msg,
4143 caption = _('Showing diagnostic orgs.'),
4144 columns = [_('Name'), _('Organization'), _('Contact'), _('Comment'), '#'],
4145 single_selection = True,
4146 refresh_callback = refresh,
4147 edit_callback = edit,
4148 new_callback = edit,
4149 delete_callback = delete
4150 )
4151
4152
4153 from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl
4154
4155 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
4156
4172
4173
4174
4175
4176
4177
4178
4179
4181 has_errors = False
4182 if self._PRW_org_unit.GetData() is None:
4183 if self._PRW_org_unit.GetValue().strip() == '':
4184 has_errors = True
4185 self._PRW_org_unit.display_as_valid(valid = False)
4186 else:
4187 self._PRW_org_unit.display_as_valid(valid = True)
4188 else:
4189 self._PRW_org_unit.display_as_valid(valid = True)
4190
4191 return (not has_errors)
4192
4203
4223
4228
4233
4235 self._refresh_as_new()
4236
4239
4240
4242
4244
4245 query = """
4246 SELECT DISTINCT ON (list_label)
4247 pk_test_org AS data,
4248 unit || ' (' || organization || ')' AS field_label,
4249 unit || ' @ ' || organization AS list_label
4250 FROM clin.v_test_orgs
4251 WHERE
4252 unit %(fragment_condition)s
4253 OR
4254 organization %(fragment_condition)s
4255 ORDER BY list_label
4256 LIMIT 50"""
4257 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4258 mp.setThresholds(1, 2, 4)
4259
4260 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4261 self.matcher = mp
4262 self.SetToolTip(_('The name of the path lab/diagnostic organisation.'))
4263 self.selection_only = False
4264
4277
4280
4281
4282
4283
4300
4301
4310
4311 def delete(meta_test_type):
4312 gmPathLab.delete_meta_type(meta_type = meta_test_type['pk'])
4313 return True
4314
4315 def get_tooltip(data):
4316 if data is None:
4317 return None
4318 return data.format(with_tests = True)
4319
4320 def refresh(lctrl):
4321 mtts = gmPathLab.get_meta_test_types()
4322 items = [ [
4323 m['abbrev'],
4324 m['name'],
4325 gmTools.coalesce(m['loinc'], ''),
4326 gmTools.coalesce(m['comment'], ''),
4327 m['pk']
4328 ] for m in mtts ]
4329 lctrl.set_string_items(items)
4330 lctrl.set_data(mtts)
4331
4332
4333 msg = _(
4334 '\n'
4335 'These are the meta test types currently defined in GNUmed.\n'
4336 '\n'
4337 'Meta test types allow you to aggregate several actual test types used\n'
4338 'by pathology labs into one logical type.\n'
4339 '\n'
4340 'This is useful for grouping together results of tests which come under\n'
4341 'different names but really are the same thing. This often happens when\n'
4342 'you switch labs or the lab starts using another test method.\n'
4343 )
4344
4345 gmListWidgets.get_choices_from_list (
4346 parent = parent,
4347 msg = msg,
4348 caption = _('Showing meta test types.'),
4349 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), '#'],
4350 single_selection = True,
4351 list_tooltip_callback = get_tooltip,
4352 edit_callback = edit,
4353 new_callback = edit,
4354 delete_callback = delete,
4355 refresh_callback = refresh
4356 )
4357
4358
4403
4404
4405 from Gnumed.wxGladeWidgets import wxgMetaTestTypeEAPnl
4406
4553
4554
4555
4556
4558 ea = cTestPanelEAPnl(parent, -1)
4559 ea.data = test_panel
4560 ea.mode = gmTools.coalesce(test_panel, 'new', 'edit')
4561 dlg = gmEditArea.cGenericEditAreaDlg2 (
4562 parent = parent,
4563 id = -1,
4564 edit_area = ea,
4565 single_entry = gmTools.bool2subst((test_panel is None), False, True)
4566 )
4567 dlg.SetTitle(gmTools.coalesce(test_panel, _('Adding new test panel'), _('Editing test panel')))
4568 if dlg.ShowModal() == wx.ID_OK:
4569 dlg.DestroyLater()
4570 return True
4571 dlg.DestroyLater()
4572 return False
4573
4574
4583
4584 def delete(test_panel):
4585 gmPathLab.delete_test_panel(pk = test_panel['pk_test_panel'])
4586 return True
4587
4588 def get_tooltip(test_panel):
4589 return test_panel.format()
4590
4591 def refresh(lctrl):
4592 panels = gmPathLab.get_test_panels(order_by = 'description')
4593 items = [ [
4594 p['description'],
4595 gmTools.coalesce(p['comment'], ''),
4596 p['pk_test_panel']
4597 ] for p in panels ]
4598 lctrl.set_string_items(items)
4599 lctrl.set_data(panels)
4600
4601 gmListWidgets.get_choices_from_list (
4602 parent = parent,
4603 caption = 'GNUmed: ' + _('Test panels list'),
4604 columns = [ _('Name'), _('Comment'), '#' ],
4605 single_selection = True,
4606 refresh_callback = refresh,
4607 edit_callback = edit,
4608 new_callback = edit,
4609 delete_callback = delete,
4610 list_tooltip_callback = get_tooltip
4611 )
4612
4613
4615
4617 query = """
4618 SELECT
4619 pk_test_panel
4620 AS data,
4621 description
4622 AS field_label,
4623 description
4624 AS list_label
4625 FROM
4626 clin.v_test_panels
4627 WHERE
4628 description %(fragment_condition)s
4629 ORDER BY field_label
4630 LIMIT 30"""
4631 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4632 mp.setThresholds(1, 2, 4)
4633
4634 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4635 self.matcher = mp
4636 self.SetToolTip(_('Select a test panel.'))
4637 self.selection_only = True
4638
4643
4648
4649
4650 from Gnumed.wxGladeWidgets import wxgTestPanelEAPnl
4651
4652 -class cTestPanelEAPnl(wxgTestPanelEAPnl.wxgTestPanelEAPnl, gmEditArea.cGenericEditAreaMixin):
4653
4673
4674
4676 self._LCTRL_loincs.set_columns([_('LOINC'), _('Term'), _('Units')])
4677 self._LCTRL_loincs.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
4678
4679 self._LCTRL_loincs.delete_callback = self._remove_loincs_from_list
4680 self.__refresh_loinc_list()
4681
4682 self._PRW_loinc.final_regex = r'.*'
4683 self._PRW_loinc.add_callback_on_selection(callback = self._on_loinc_selected)
4684
4685
4687 self._LCTRL_loincs.remove_items_safely()
4688 if self.__loincs is None:
4689 if self.data is None:
4690 return
4691 self.__loincs = self.data['loincs']
4692
4693 items = []
4694 for loinc in self.__loincs:
4695 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4696 if loinc_detail is None:
4697
4698 ttypes = gmPathLab.get_measurement_types(loincs = [loinc])
4699 if len(ttypes) == 0:
4700 items.append([loinc, _('LOINC not found'), ''])
4701 else:
4702 for tt in ttypes:
4703 items.append([loinc, _('not a LOINC') + u'; %(name)s @ %(name_org)s [#%(pk_test_type)s]' % tt, ''])
4704 continue
4705 items.append ([
4706 loinc,
4707 loinc_detail['term'],
4708 gmTools.coalesce(loinc_detail['example_units'], '', '%s')
4709 ])
4710
4711 self._LCTRL_loincs.set_string_items(items)
4712 self._LCTRL_loincs.set_column_widths()
4713
4714
4715
4716
4718 validity = True
4719
4720 if self.__loincs is None:
4721 if self.data is not None:
4722 self.__loincs = self.data['loincs']
4723
4724 if self.__loincs is None:
4725
4726 self.StatusText = _('No LOINC codes selected.')
4727 self._PRW_loinc.SetFocus()
4728
4729 if self._TCTRL_description.GetValue().strip() == '':
4730 validity = False
4731 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False)
4732 self._TCTRL_description.SetFocus()
4733 else:
4734 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True)
4735
4736 return validity
4737
4738
4747
4748
4750 self.data['description'] = self._TCTRL_description.GetValue().strip()
4751 self.data['comment'] = self._TCTRL_comment.GetValue().strip()
4752 self.data.save()
4753 if self.__loincs is not None:
4754 self.data.included_loincs = self.__loincs
4755 return True
4756
4757
4759 self._TCTRL_description.SetValue('')
4760 self._TCTRL_comment.SetValue('')
4761 self._PRW_loinc.SetText('', None)
4762 self._LBL_loinc.SetLabel('')
4763 self.__loincs = None
4764 self.__refresh_loinc_list()
4765
4766 self._TCTRL_description.SetFocus()
4767
4768
4770 self._refresh_as_new()
4771
4772
4774 self._TCTRL_description.SetValue(self.data['description'])
4775 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
4776 self._PRW_loinc.SetText('', None)
4777 self._LBL_loinc.SetLabel('')
4778 self.__loincs = self.data['loincs']
4779 self.__refresh_loinc_list()
4780
4781 self._PRW_loinc.SetFocus()
4782
4783
4784
4785
4787 loinc = self._PRW_loinc.GetData()
4788 if loinc is None:
4789 self._LBL_loinc.SetLabel('')
4790 return
4791 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4792 if loinc_detail is None:
4793 loinc_str = _('no LOINC details found')
4794 else:
4795 loinc_str = '%s: %s%s' % (
4796 loinc,
4797 loinc_detail['term'],
4798 gmTools.coalesce(loinc_detail['example_units'], '', ' (%s)')
4799 )
4800 self._LBL_loinc.SetLabel(loinc_str)
4801
4802
4824
4825
4829
4830
4832 loincs2remove = self._LCTRL_loincs.selected_item_data
4833 if loincs2remove is None:
4834 return
4835 for loinc in loincs2remove:
4836 try:
4837 while True:
4838 self.__loincs.remove(loinc[0])
4839 except ValueError:
4840 pass
4841 self.__refresh_loinc_list()
4842
4843
4844
4845
4846 if __name__ == '__main__':
4847
4848 from Gnumed.pycommon import gmLog2
4849 from Gnumed.wxpython import gmPatSearchWidgets
4850
4851 gmI18N.activate_locale()
4852 gmI18N.install_domain()
4853 gmDateTime.init()
4854
4855
4863
4871
4872
4873
4874
4875
4876
4877
4878 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
4879
4880 test_test_ea_pnl()
4881
4882
4883
4884