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
921 self._LCTRL_days.set_columns([_('Day')])
922 self._LCTRL_results.set_columns([_('Time'), _('Test'), _('Result'), _('Reference')])
923 self._LCTRL_results.edit_callback = self._on_edit
924 self._PNL_related_documents.lab_reference = None
925
926
929
930
936
937
956
957
964
965
966
967
969 if self.__patient is None:
970 return True
971
972 if kwds['pk_identity'] is not None:
973 if kwds['pk_identity'] != self.__patient.ID:
974 return True
975
976 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
977 return True
978
979 self._schedule_data_reget()
980 return True
981
982
984 event.Skip()
985
986 day = self._LCTRL_days.get_item_data(item_idx = event.Index)['clin_when_day']
987 results = self.__patient.emr.get_results_for_day(timestamp = day)
988 items = []
989 data = []
990 for r in results:
991 range_info = gmTools.coalesce (
992 r.formatted_clinical_range,
993 r.formatted_normal_range
994 )
995 review = gmTools.bool2subst (
996 r['reviewed'],
997 '',
998 ' ' + gmTools.u_writing_hand,
999 ' ' + gmTools.u_writing_hand
1000 )
1001 items.append ([
1002 gmDateTime.pydt_strftime(r['clin_when'], '%H:%M'),
1003 r['abbrev_tt'],
1004 '%s%s%s%s' % (
1005 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1006 gmTools.coalesce(r['val_unit'], '', ' %s'),
1007 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1008 review
1009 ),
1010 gmTools.coalesce(range_info, '')
1011 ])
1012 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
1013
1014 self._LCTRL_results.set_string_items(items)
1015 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1016 self._LCTRL_results.set_data(data)
1017 self._LCTRL_results.Select(idx = 0, on = 1)
1018
1019
1025
1026
1027
1028
1030 self.__repopulate_ui()
1031 return True
1032
1033
1034
1035
1037 return self.__patient
1038
1040 if (self.__patient is None) and (patient is None):
1041 return
1042 if patient is None:
1043 self.__patient = None
1044 self.__clear()
1045 return
1046 if self.__patient is None:
1047 self.__patient = patient
1048 self._schedule_data_reget()
1049 return
1050 if self.__patient.ID == patient.ID:
1051 return
1052 self.__patient = patient
1053 self._schedule_data_reget()
1054
1055 patient = property(_get_patient, _set_patient)
1056
1057
1058 from Gnumed.wxGladeWidgets import wxgMeasurementsByIssuePnl
1059
1060 -class cMeasurementsByIssuePnl(wxgMeasurementsByIssuePnl.wxgMeasurementsByIssuePnl, gmRegetMixin.cRegetOnPaintMixin):
1061 """A class for displaying measurement results as a list partitioned by issue/episode.
1062
1063 - operates on a cPatient instance handed to it and NOT on the currently active patient
1064 """
1074
1075
1076
1077
1079 self._LCTRL_issues.set_columns([_('Problem')])
1080 self._LCTRL_results.set_columns([_('When'), _('Test'), _('Result'), _('Reference')])
1081 self._PNL_related_documents.lab_reference = None
1082
1083
1089
1090
1096
1097
1117
1118
1125
1126
1127
1128
1130 if self.__patient is None:
1131 return True
1132
1133 if kwds['pk_identity'] is not None:
1134 if kwds['pk_identity'] != self.__patient.ID:
1135 return True
1136
1137 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1138 return True
1139
1140 self._schedule_data_reget()
1141 return True
1142
1143
1145 event.Skip()
1146
1147 pk_issue = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_issue']
1148 if pk_issue is None:
1149 pk_episode = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_episode']
1150 results = self.__patient.emr.get_results_for_episode(pk_episode = pk_episode)
1151 else:
1152 results = self.__patient.emr.get_results_for_issue(pk_health_issue = pk_issue)
1153 items = []
1154 data = []
1155 for r in results:
1156 range_info = gmTools.coalesce (
1157 r.formatted_clinical_range,
1158 r.formatted_normal_range
1159 )
1160 review = gmTools.bool2subst (
1161 r['reviewed'],
1162 '',
1163 ' ' + gmTools.u_writing_hand,
1164 ' ' + gmTools.u_writing_hand
1165 )
1166 items.append ([
1167 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M'),
1168 r['abbrev_tt'],
1169 '%s%s%s%s' % (
1170 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1171 gmTools.coalesce(r['val_unit'], '', ' %s'),
1172 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1173 review
1174 ),
1175 gmTools.coalesce(range_info, '')
1176 ])
1177 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
1178
1179 self._LCTRL_results.set_string_items(items)
1180 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1181 self._LCTRL_results.set_data(data)
1182 self._LCTRL_results.Select(idx = 0, on = 1)
1183 self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted'])
1184
1185
1191
1192
1193
1194
1196 self.__repopulate_ui()
1197 return True
1198
1199
1200
1201
1203 return self.__patient
1204
1206 if (self.__patient is None) and (patient is None):
1207 return
1208 if patient is None:
1209 self.__patient = None
1210 self.__clear()
1211 return
1212 if self.__patient is None:
1213 self.__patient = patient
1214 self._schedule_data_reget()
1215 return
1216 if self.__patient.ID == patient.ID:
1217 return
1218 self.__patient = patient
1219 self._schedule_data_reget()
1220
1221 patient = property(_get_patient, _set_patient)
1222
1223
1224 from Gnumed.wxGladeWidgets import wxgMeasurementsByBatteryPnl
1225
1227 """A grid class for displaying measurement results filtered by battery/panel.
1228
1229 - operates on a cPatient instance handed to it and NOT on the currently active patient
1230 """
1240
1241
1242
1243
1246
1247
1253
1254
1256 self._GRID_results_battery.patient = self.__patient
1257 return True
1258
1259
1261 if panel is None:
1262 self._TCTRL_panel_comment.SetValue('')
1263 self._GRID_results_battery.panel_to_show = None
1264 else:
1265 pnl = self._PRW_panel.GetData(as_instance = True)
1266 self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
1267 pnl['comment'],
1268 ''
1269 ))
1270 self._GRID_results_battery.panel_to_show = pnl
1271
1272
1273
1275 self._TCTRL_panel_comment.SetValue('')
1276 if self._PRW_panel.GetValue().strip() == '':
1277 self._GRID_results_battery.panel_to_show = None
1278
1279
1280
1281
1282
1284 if self.__patient is None:
1285 return True
1286
1287 if kwds['pk_identity'] is not None:
1288 if kwds['pk_identity'] != self.__patient.ID:
1289 return True
1290
1291 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1292 return True
1293
1294 self._schedule_data_reget()
1295 return True
1296
1297
1300
1301
1303 wx.CallAfter(self.__on_panel_selected, panel=panel)
1304
1305
1307 wx.CallAfter(self.__on_panel_selection_modified)
1308
1309
1310
1311
1313 self.__repopulate_ui()
1314 return True
1315
1316
1317
1318
1320 return self.__patient
1321
1323 if (self.__patient is None) and (patient is None):
1324 return
1325 if (self.__patient is None) or (patient is None):
1326 self.__patient = patient
1327 self._schedule_data_reget()
1328 return
1329 if self.__patient.ID == patient.ID:
1330 return
1331 self.__patient = patient
1332 self._schedule_data_reget()
1333
1334 patient = property(_get_patient, _set_patient)
1335
1336
1337 from Gnumed.wxGladeWidgets import wxgMeasurementsAsMostRecentListPnl
1338
1340 """A list ctrl class for displaying measurement results.
1341
1342 - most recent results
1343 - possibly filtered by battery/panel
1344
1345 - operates on a cPatient instance handed to it and NOT on the currently active patient
1346 """
1356
1357
1358
1359
1361 self._LCTRL_results.set_columns([_('Test'), _('Result'), _('When'), _('Range')])
1362 self._CHBOX_show_missing.Disable()
1363 self._PNL_related_documents.lab_reference = None
1364
1365
1374
1375
1377
1378 self._TCTRL_details.SetValue('')
1379 self._PNL_related_documents.lab_reference = None
1380 if self.__patient is None:
1381 self._LCTRL_results.remove_items_safely()
1382 return
1383
1384 pnl = self._PRW_panel.GetData(as_instance = True)
1385 if pnl is None:
1386 results = gmPathLab.get_most_recent_result_for_test_types (
1387 pk_patient = self.__patient.ID,
1388 consider_meta_type = True
1389 )
1390 else:
1391 results = pnl.get_most_recent_results (
1392 pk_patient = self.__patient.ID,
1393
1394 group_by_meta_type = True,
1395 include_missing = self._CHBOX_show_missing.IsChecked()
1396 )
1397 items = []
1398 data = []
1399 for r in results:
1400 if isinstance(r, gmPathLab.cTestResult):
1401 result_type = gmTools.coalesce (
1402 value2test = r['pk_meta_test_type'],
1403 return_instead = r['abbrev_tt'],
1404 value2return = '%s%s' % (gmTools.u_sum, r['abbrev_meta'])
1405 )
1406 review = gmTools.bool2subst (
1407 r['reviewed'],
1408 '',
1409 ' ' + gmTools.u_writing_hand,
1410 ' ' + gmTools.u_writing_hand
1411 )
1412 result_val = '%s%s%s%s' % (
1413 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1414 gmTools.coalesce(r['val_unit'], '', ' %s'),
1415 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1416 review
1417 )
1418 result_when = _('%s ago (%s)') % (
1419 gmDateTime.format_interval_medically(interval = gmDateTime.pydt_now_here() - r['clin_when']),
1420 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes)
1421 )
1422 range_info = gmTools.coalesce (
1423 r.formatted_clinical_range,
1424 r.formatted_normal_range
1425 )
1426 tt = r.format(with_source_data = True)
1427 else:
1428 result_type = r
1429 result_val = _('missing')
1430 loinc_data = gmLOINC.loinc2data(r)
1431 if loinc_data is None:
1432 result_when = _('LOINC not found')
1433 tt = u''
1434 else:
1435 result_when = loinc_data['term']
1436 tt = gmLOINC.format_loinc(r)
1437 range_info = None
1438 items.append([result_type, result_val, result_when, gmTools.coalesce(range_info, '')])
1439 data.append({'data': r, 'formatted': tt})
1440
1441 self._LCTRL_results.set_string_items(items)
1442 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1443 self._LCTRL_results.set_data(data)
1444
1445 if len(items) > 0:
1446 self._LCTRL_results.Select(idx = 0, on = 1)
1447 self._LCTRL_results.SetFocus()
1448
1449 return True
1450
1451
1453 if panel is None:
1454 self._TCTRL_panel_comment.SetValue('')
1455 self._CHBOX_show_missing.Disable()
1456 else:
1457 pnl = self._PRW_panel.GetData(as_instance = True)
1458 self._TCTRL_panel_comment.SetValue(gmTools.coalesce(pnl['comment'], ''))
1459 self.__repopulate_ui()
1460 self._CHBOX_show_missing.Enable()
1461
1462
1464 self._TCTRL_panel_comment.SetValue('')
1465 if self._PRW_panel.Value.strip() == u'':
1466 self.__repopulate_ui()
1467 self._CHBOX_show_missing.Disable()
1468
1469
1470
1471
1473 if self.__patient is None:
1474 return True
1475
1476 if kwds['pk_identity'] is not None:
1477 if kwds['pk_identity'] != self.__patient.ID:
1478 return True
1479
1480 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results', 'clin.test_panel']:
1481 return True
1482
1483 self._schedule_data_reget()
1484 return True
1485
1486
1489
1490
1492 wx.CallAfter(self.__on_panel_selected, panel = panel)
1493
1494
1496 wx.CallAfter(self.__on_panel_selection_modified)
1497
1498
1507
1508
1516
1517
1519 event.Skip()
1520
1521 if self._PRW_panel.GetData(as_instance = False) is None:
1522 return
1523 self.__repopulate_ui()
1524
1525
1526
1527
1529 self.__repopulate_ui()
1530 return True
1531
1532
1533
1534
1536 return self.__patient
1537
1539 if (self.__patient is None) and (patient is None):
1540 return
1541 if (self.__patient is None) or (patient is None):
1542 self.__patient = patient
1543 self._schedule_data_reget()
1544 return
1545 if self.__patient.ID == patient.ID:
1546 return
1547 self.__patient = patient
1548 self._schedule_data_reget()
1549
1550 patient = property(_get_patient, _set_patient)
1551
1552
1553 from Gnumed.wxGladeWidgets import wxgMeasurementsAsTablePnl
1554
1555 -class cMeasurementsAsTablePnl(wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl, gmRegetMixin.cRegetOnPaintMixin):
1556 """A panel for holding a grid displaying all measurement results.
1557
1558 - operates on a cPatient instance handed to it and NOT on the currently active patient
1559 """
1569
1570
1571
1572
1574 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
1575
1576 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
1577 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
1578
1579 item = self.__action_button_popup.Append(-1, _('Plot'))
1580 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590 item = self.__action_button_popup.Append(-1, _('&Delete'))
1591 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
1592
1593
1594
1595
1596 self._GRID_results_all.show_by_panel = False
1597
1598
1601
1602
1604 self._GRID_results_all.patient = self.__patient
1605
1606 self.Layout()
1607 return True
1608
1609
1612
1613
1616
1617
1620
1621
1622
1623
1625 if self.__patient is None:
1626 return True
1627
1628 if kwds['pk_identity'] is not None:
1629 if kwds['pk_identity'] != self.__patient.ID:
1630 return True
1631
1632 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1633 return True
1634
1635 self._schedule_data_reget()
1636 return True
1637
1638
1641
1642
1646
1647
1650
1651
1657
1658
1659
1660
1662 self.__repopulate_ui()
1663 return True
1664
1665
1666
1667
1669 return self.__patient
1670
1672 if (self.__patient is None) and (patient is None):
1673 return
1674 if (self.__patient is None) or (patient is None):
1675 self.__patient = patient
1676 self._schedule_data_reget()
1677 return
1678 if self.__patient.ID == patient.ID:
1679 return
1680 self.__patient = patient
1681 self._schedule_data_reget()
1682
1683 patient = property(_get_patient, _set_patient)
1684
1685
1686
1687
1689 """Notebook displaying measurements pages:
1690
1691 - by test battery
1692 - by day
1693 - by issue/episode
1694 - most-recent list, perhaps by panel
1695 - full grid
1696 - full list
1697
1698 Used as a main notebook plugin page.
1699
1700 Operates on the active patient.
1701 """
1702
1717
1718
1719
1720
1722 for page_idx in range(self.GetPageCount()):
1723 page = self.GetPage(page_idx)
1724 page.patient = None
1725
1726
1727 - def _post_patient_selection(self, **kwds):
1728 for page_idx in range(self.GetPageCount()):
1729 page = self.GetPage(page_idx)
1730 page.patient = self.__patient.patient
1731
1732
1733
1734
1736 if self.__patient.connected:
1737 pat = self.__patient.patient
1738 else:
1739 pat = None
1740 for page_idx in range(self.GetPageCount()):
1741 page = self.GetPage(page_idx)
1742 page.patient = pat
1743
1744 return True
1745
1746
1747
1748
1750
1751
1752 new_page = cMeasurementsByDayPnl(self, -1)
1753 new_page.patient = None
1754 self.AddPage (
1755 page = new_page,
1756 text = _('Days'),
1757 select = True
1758 )
1759
1760
1761 new_page = cMeasurementsByIssuePnl(self, -1)
1762 new_page.patient = None
1763 self.AddPage (
1764 page = new_page,
1765 text = _('Problems'),
1766 select = False
1767 )
1768
1769
1770 new_page = cMeasurementsByBatteryPnl(self, -1)
1771 new_page.patient = None
1772 self.AddPage (
1773 page = new_page,
1774 text = _('Panels'),
1775 select = False
1776 )
1777
1778
1779 new_page = cMeasurementsAsMostRecentListPnl(self, -1)
1780 new_page.patient = None
1781 self.AddPage (
1782 page = new_page,
1783 text = _('Most recent'),
1784 select = False
1785 )
1786
1787
1788 new_page = cMeasurementsAsTablePnl(self, -1)
1789 new_page.patient = None
1790 self.AddPage (
1791 page = new_page,
1792 text = _('Table'),
1793 select = False
1794 )
1795
1796
1797 new_page = cMeasurementsAsListPnl(self, -1)
1798 new_page.patient = None
1799 self.AddPage (
1800 page = new_page,
1801 text = _('List'),
1802 select = False
1803 )
1804
1805
1806
1807
1809 return self.__patient
1810
1812 self.__patient = patient
1813 if self.__patient.connected:
1814 pat = self.__patient.patient
1815 else:
1816 pat = None
1817 for page_idx in range(self.GetPageCount()):
1818 page = self.GetPage(page_idx)
1819 page.patient = pat
1820
1821 patient = property(_get_patient, _set_patient)
1822
1823
1825 """A grid class for displaying measurement results.
1826
1827 - operates on a cPatient instance handed to it
1828 - does NOT listen to the currently active patient
1829 - thereby it can display any patient at any time
1830 """
1831
1832
1833
1834
1835
1837
1838 wx.grid.Grid.__init__(self, *args, **kwargs)
1839
1840 self.__patient = None
1841 self.__panel_to_show = None
1842 self.__show_by_panel = False
1843 self.__cell_data = {}
1844 self.__row_label_data = []
1845 self.__col_label_data = []
1846
1847 self.__prev_row = None
1848 self.__prev_col = None
1849 self.__prev_label_row = None
1850 self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::'))
1851
1852 self.__init_ui()
1853 self.__register_events()
1854
1855
1856
1857
1859 if not self.IsSelection():
1860 gmDispatcher.send(signal = 'statustext', msg = _('No results selected for deletion.'))
1861 return True
1862
1863 selected_cells = self.get_selected_cells()
1864 if len(selected_cells) > 20:
1865 results = None
1866 msg = _(
1867 'There are %s results marked for deletion.\n'
1868 '\n'
1869 'Are you sure you want to delete these results ?'
1870 ) % len(selected_cells)
1871 else:
1872 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1873 txt = '\n'.join([ '%s %s (%s): %s %s%s' % (
1874 r['clin_when'].strftime('%x %H:%M'),
1875 r['unified_abbrev'],
1876 r['unified_name'],
1877 r['unified_val'],
1878 r['val_unit'],
1879 gmTools.coalesce(r['abnormality_indicator'], '', ' (%s)')
1880 ) for r in results
1881 ])
1882 msg = _(
1883 'The following results are marked for deletion:\n'
1884 '\n'
1885 '%s\n'
1886 '\n'
1887 'Are you sure you want to delete these results ?'
1888 ) % txt
1889
1890 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
1891 self,
1892 -1,
1893 caption = _('Deleting test results'),
1894 question = msg,
1895 button_defs = [
1896 {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False},
1897 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True}
1898 ]
1899 )
1900 decision = dlg.ShowModal()
1901
1902 if decision == wx.ID_YES:
1903 if results is None:
1904 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1905 for result in results:
1906 gmPathLab.delete_test_result(result)
1907
1908
1910 if not self.IsSelection():
1911 gmDispatcher.send(signal = 'statustext', msg = _('Cannot sign results. No results selected.'))
1912 return True
1913
1914 selected_cells = self.get_selected_cells()
1915 tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1916
1917 return review_tests(parent = self, tests = tests)
1918
1919
1921
1922 if not self.IsSelection():
1923 gmDispatcher.send(signal = 'statustext', msg = _('Cannot plot results. No results selected.'))
1924 return True
1925
1926 tests = self.__cells_to_data (
1927 cells = self.get_selected_cells(),
1928 exclude_multi_cells = False,
1929 auto_include_multi_cells = True
1930 )
1931
1932 plot_measurements(parent = self, tests = tests)
1933
1934
1936 """Assemble list of all selected cells."""
1937
1938 all_selected_cells = []
1939
1940 all_selected_cells += [ cell_coords.Get() for cell_coords in self.GetSelectedCells() ]
1941
1942 fully_selected_rows = self.GetSelectedRows()
1943 all_selected_cells += list (
1944 (row, col)
1945 for row in fully_selected_rows
1946 for col in range(self.GetNumberCols())
1947 )
1948
1949 fully_selected_cols = self.GetSelectedCols()
1950 all_selected_cells += list (
1951 (row, col)
1952 for row in range(self.GetNumberRows())
1953 for col in fully_selected_cols
1954 )
1955
1956 selected_blocks = zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight())
1957 for top_left_corner, bottom_right_corner in selected_blocks:
1958 all_selected_cells += [
1959 (row, col)
1960 for row in range(top_left_corner[0], bottom_right_corner[0] + 1)
1961 for col in range(top_left_corner[1], bottom_right_corner[1] + 1)
1962 ]
1963 return set(all_selected_cells)
1964
1965
1966 - def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
1967 """Select a range of cells according to criteria.
1968
1969 unsigned_only: include only those which are not signed at all yet
1970 accountable_only: include only those for which the current user is responsible
1971 keep_preselections: broaden (rather than replace) the range of selected cells
1972
1973 Combinations are powerful !
1974 """
1975 wx.BeginBusyCursor()
1976 self.BeginBatch()
1977
1978 if not keep_preselections:
1979 self.ClearSelection()
1980
1981 for col_idx in self.__cell_data.keys():
1982 for row_idx in self.__cell_data[col_idx].keys():
1983
1984
1985 do_not_include = False
1986 for result in self.__cell_data[col_idx][row_idx]:
1987 if unsigned_only:
1988 if result['reviewed']:
1989 do_not_include = True
1990 break
1991 if accountables_only:
1992 if not result['you_are_responsible']:
1993 do_not_include = True
1994 break
1995 if do_not_include:
1996 continue
1997
1998 self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True)
1999
2000 self.EndBatch()
2001 wx.EndBusyCursor()
2002
2003
2005 self.empty_grid()
2006 if self.__patient is None:
2007 return
2008
2009 if self.__show_by_panel:
2010 if self.__panel_to_show is None:
2011 return
2012 tests = self.__panel_to_show.get_test_types_for_results (
2013 self.__patient.ID,
2014 order_by = 'unified_abbrev',
2015 unique_meta_types = True
2016 )
2017 self.__repopulate_grid (
2018 tests4rows = tests,
2019 test_pks2show = [ tt['pk_test_type'] for tt in self.__panel_to_show['test_types'] ]
2020 )
2021 return
2022
2023 emr = self.__patient.emr
2024 tests = emr.get_test_types_for_results(order_by = 'unified_abbrev', unique_meta_types = True)
2025 self.__repopulate_grid(tests4rows = tests)
2026
2027
2029
2030 if len(tests4rows) == 0:
2031 return
2032
2033 emr = self.__patient.emr
2034
2035 self.__row_label_data = tests4rows
2036 row_labels = [ '%s%s' % (
2037 gmTools.bool2subst(test_type['is_fake_meta_type'], '', gmTools.u_sum, ''),
2038 test_type['unified_abbrev']
2039 ) for test_type in self.__row_label_data
2040 ]
2041
2042 self.__col_label_data = [ d['clin_when_day'] for d in emr.get_dates_for_results (
2043 tests = test_pks2show,
2044 reverse_chronological = True
2045 )]
2046 col_labels = [ gmDateTime.pydt_strftime(date, self.__date_format, accuracy = gmDateTime.acc_days) for date in self.__col_label_data ]
2047
2048 results = emr.get_test_results_by_date (
2049 tests = test_pks2show,
2050 reverse_chronological = True
2051 )
2052
2053 self.BeginBatch()
2054
2055
2056 self.AppendRows(numRows = len(row_labels))
2057 for row_idx in range(len(row_labels)):
2058 self.SetRowLabelValue(row_idx, row_labels[row_idx])
2059
2060
2061 self.AppendCols(numCols = len(col_labels))
2062 for col_idx in range(len(col_labels)):
2063 self.SetColLabelValue(col_idx, col_labels[col_idx])
2064
2065
2066 for result in results:
2067 row_idx = row_labels.index('%s%s' % (
2068 gmTools.bool2subst(result['is_fake_meta_type'], '', gmTools.u_sum, ''),
2069 result['unified_abbrev']
2070 ))
2071 col_idx = col_labels.index(gmDateTime.pydt_strftime(result['clin_when'], self.__date_format, accuracy = gmDateTime.acc_days))
2072
2073 try:
2074 self.__cell_data[col_idx]
2075 except KeyError:
2076 self.__cell_data[col_idx] = {}
2077
2078
2079 if row_idx in self.__cell_data[col_idx]:
2080 self.__cell_data[col_idx][row_idx].append(result)
2081 self.__cell_data[col_idx][row_idx].sort(key = lambda x: x['clin_when'], reverse = True)
2082 else:
2083 self.__cell_data[col_idx][row_idx] = [result]
2084
2085
2086 vals2display = []
2087 cell_has_out_of_bounds_value = False
2088 for sub_result in self.__cell_data[col_idx][row_idx]:
2089
2090 if sub_result.is_considered_abnormal:
2091 cell_has_out_of_bounds_value = True
2092
2093 abnormality_indicator = sub_result.formatted_abnormality_indicator
2094 if abnormality_indicator is None:
2095 abnormality_indicator = ''
2096 if abnormality_indicator != '':
2097 abnormality_indicator = ' (%s)' % abnormality_indicator[:3]
2098
2099 missing_review = False
2100
2101
2102 if not sub_result['reviewed']:
2103 missing_review = True
2104
2105 else:
2106
2107 if sub_result['you_are_responsible'] and not sub_result['review_by_you']:
2108 missing_review = True
2109
2110 needs_superscript = False
2111
2112
2113 if sub_result.is_long_text:
2114 lines = gmTools.strip_empty_lines (
2115 text = sub_result['unified_val'],
2116 eol = '\n',
2117 return_list = True
2118 )
2119 needs_superscript = True
2120 tmp = lines[0][:7]
2121 else:
2122 val = gmTools.strip_empty_lines (
2123 text = sub_result['unified_val'],
2124 eol = '\n',
2125 return_list = False
2126 ).replace('\n', '//')
2127 if len(val) > 8:
2128 needs_superscript = True
2129 tmp = val[:7]
2130 else:
2131 tmp = '%.8s' % val[:8]
2132
2133
2134 tmp = '%s%.6s' % (tmp, abnormality_indicator)
2135
2136
2137 has_sub_result_comment = gmTools.coalesce (
2138 gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']),
2139 ''
2140 ).strip() != ''
2141 if has_sub_result_comment:
2142 needs_superscript = True
2143
2144 if needs_superscript:
2145 tmp = '%s%s' % (tmp, gmTools.u_superscript_one)
2146
2147
2148 if missing_review:
2149 tmp = '%s %s' % (tmp, gmTools.u_writing_hand)
2150 else:
2151 if sub_result['is_clinically_relevant']:
2152 tmp += ' !'
2153
2154
2155 if len(self.__cell_data[col_idx][row_idx]) > 1:
2156 tmp = '%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp)
2157
2158 vals2display.append(tmp)
2159
2160 self.SetCellValue(row_idx, col_idx, '\n'.join(vals2display))
2161 self.SetCellAlignment(row_idx, col_idx, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172 if cell_has_out_of_bounds_value:
2173
2174 self.SetCellBackgroundColour(row_idx, col_idx, 'PALE TURQUOISE')
2175
2176 self.EndBatch()
2177
2178 self.AutoSize()
2179 self.AdjustScrollbars()
2180 self.ForceRefresh()
2181
2182
2183
2184 return
2185
2186
2188 self.BeginBatch()
2189 self.ClearGrid()
2190
2191
2192 if self.GetNumberRows() > 0:
2193 self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
2194 if self.GetNumberCols() > 0:
2195 self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
2196 self.EndBatch()
2197 self.__cell_data = {}
2198 self.__row_label_data = []
2199 self.__col_label_data = []
2200
2201
2219
2220
2246
2247
2248
2249
2251
2252 self.SetMinSize((10, 10))
2253
2254 self.CreateGrid(0, 1)
2255 self.EnableEditing(0)
2256 self.EnableDragGridSize(1)
2257
2258
2259
2260
2261
2262
2263 self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE)
2264
2265 self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE)
2266 font = self.GetLabelFont()
2267 font.SetWeight(wx.FONTWEIGHT_LIGHT)
2268 self.SetLabelFont(font)
2269
2270
2271 dbcfg = gmCfg.cCfgSQL()
2272 url = dbcfg.get2 (
2273 option = 'external.urls.measurements_encyclopedia',
2274 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
2275 bias = 'user',
2276 default = gmPathLab.URL_test_result_information
2277 )
2278
2279 self.__WIN_corner = self.GetGridCornerLabelWindow()
2280
2281 LNK_lab = wxh.HyperlinkCtrl (
2282 self.__WIN_corner,
2283 -1,
2284 label = _('Tests'),
2285 style = wxh.HL_DEFAULT_STYLE
2286 )
2287 LNK_lab.SetURL(url)
2288 LNK_lab.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND))
2289 LNK_lab.SetToolTip(_(
2290 'Navigate to an encyclopedia of measurements\n'
2291 'and test methods on the web.\n'
2292 '\n'
2293 ' <%s>'
2294 ) % url)
2295
2296 SZR_inner = wx.BoxSizer(wx.HORIZONTAL)
2297 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)
2298 SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0)
2299 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)
2300
2301 SZR_corner = wx.BoxSizer(wx.VERTICAL)
2302 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)
2303 SZR_corner.Add(SZR_inner, 0, wx.EXPAND)
2304 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)
2305
2306 self.__WIN_corner.SetSizer(SZR_corner)
2307 SZR_corner.Fit(self.__WIN_corner)
2308
2309
2311 self.__WIN_corner.Layout()
2312
2313
2314 - def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
2315 """List of <cells> must be in row / col order."""
2316 data = []
2317 for row, col in cells:
2318 try:
2319
2320 data_list = self.__cell_data[col][row]
2321 except KeyError:
2322 continue
2323
2324 if len(data_list) == 1:
2325 data.append(data_list[0])
2326 continue
2327
2328 if exclude_multi_cells:
2329 gmDispatcher.send(signal = 'statustext', msg = _('Excluding multi-result field from further processing.'))
2330 continue
2331
2332 if auto_include_multi_cells:
2333 data.extend(data_list)
2334 continue
2335
2336 data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list)
2337 if data_to_include is None:
2338 continue
2339 data.extend(data_to_include)
2340
2341 return data
2342
2343
2345 data = gmListWidgets.get_choices_from_list (
2346 parent = self,
2347 msg = _(
2348 'Your selection includes a field with multiple results.\n'
2349 '\n'
2350 'Please select the individual results you want to work on:'
2351 ),
2352 caption = _('Selecting test results'),
2353 choices = [ [d['clin_when'], '%s: %s' % (d['abbrev_tt'], d['name_tt']), d['unified_val']] for d in cell_data ],
2354 columns = [ _('Date / Time'), _('Test'), _('Result') ],
2355 data = cell_data,
2356 single_selection = single_selection
2357 )
2358 return data
2359
2360
2361
2362
2364
2365 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
2366 self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels)
2367
2368
2369
2370 self.Bind(wx.EVT_SIZE, self.__resize_corner_window)
2371
2372
2373 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
2374
2375
2377 col = evt.GetCol()
2378 row = evt.GetRow()
2379
2380 try:
2381 self.__cell_data[col][row]
2382 except KeyError:
2383 presets = {}
2384 col_date = self.__col_label_data[col]
2385 presets['clin_when'] = {'data': col_date}
2386 test_type = self.__row_label_data[row]
2387 if test_type['pk_meta_test_type'] is not None:
2388 temporally_closest_result_of_row_type = test_type.meta_test_type.get_temporally_closest_result(col_date, self.__patient.ID)
2389 if temporally_closest_result_of_row_type is not None:
2390
2391
2392 presets['pk_test_type'] = {'data': temporally_closest_result_of_row_type['pk_test_type']}
2393
2394
2395
2396 same_day_results = gmPathLab.get_results_for_day (
2397 timestamp = col_date,
2398 patient = self.__patient.ID,
2399 order_by = None
2400 )
2401 if len(same_day_results) > 0:
2402
2403
2404 presets['pk_episode'] = {'data': same_day_results[0]['pk_episode']}
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414 edit_measurement (
2415 parent = self,
2416 measurement = None,
2417 single_entry = True,
2418 presets = presets
2419 )
2420 return
2421
2422 if len(self.__cell_data[col][row]) > 1:
2423 data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True)
2424 else:
2425 data = self.__cell_data[col][row][0]
2426
2427 if data is None:
2428 return
2429
2430 edit_measurement(parent = self, measurement = data, single_entry = True)
2431
2432
2433
2434
2435
2436
2437
2438
2440
2441
2442
2443 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2444
2445 row = self.YToRow(y)
2446
2447 if self.__prev_label_row == row:
2448 return
2449
2450 self.__prev_label_row == row
2451
2452 evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row))
2453
2454
2455
2456
2457
2458
2459
2460
2462 """Calculate where the mouse is and set the tooltip dynamically."""
2463
2464
2465
2466 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480 row, col = self.XYToCell(x, y)
2481
2482 if (row == self.__prev_row) and (col == self.__prev_col):
2483 return
2484
2485 self.__prev_row = row
2486 self.__prev_col = col
2487
2488 evt.GetEventObject().SetToolTip(self.get_cell_tooltip(col=col, row=row))
2489
2490
2491
2492
2494 return self.__patient
2495
2499
2500 patient = property(_get_patient, _set_patient)
2501
2505
2506 panel_to_show = property(lambda x:x, _set_panel_to_show)
2507
2511
2512 show_by_panel = property(lambda x:x, _set_show_by_panel)
2513
2514
2515
2516
2517 from Gnumed.wxGladeWidgets import wxgMeasurementsPnl
2518
2519 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
2520 """Panel holding a grid with lab data. Used as notebook page."""
2521
2529
2530
2531
2533 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
2534 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
2535 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._schedule_data_reget)
2536 gmDispatcher.connect(signal = 'clin.reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
2537
2539 self._schedule_data_reget()
2540
2542 self._GRID_results_all.patient = None
2543 self._GRID_results_battery.patient = None
2544
2547
2551
2555
2558
2564
2567
2587
2590
2593
2596
2598 wx.CallAfter(self.__on_panel_selected, panel=panel)
2599
2601 if panel is None:
2602 self._TCTRL_panel_comment.SetValue('')
2603 self._GRID_results_battery.panel_to_show = None
2604
2605 self._PNL_results_battery_grid.Hide()
2606 else:
2607 pnl = self._PRW_panel.GetData(as_instance = True)
2608 self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
2609 pnl['comment'],
2610 ''
2611 ))
2612 self._GRID_results_battery.panel_to_show = pnl
2613
2614 self._PNL_results_battery_grid.Show()
2615 self._GRID_results_battery.Fit()
2616 self._GRID_results_all.Fit()
2617 self.Layout()
2618
2620 wx.CallAfter(self.__on_panel_selection_modified)
2621
2623 self._TCTRL_panel_comment.SetValue('')
2624 if self._PRW_panel.GetValue().strip() == '':
2625 self._GRID_results_battery.panel_to_show = None
2626
2627 self._PNL_results_battery_grid.Hide()
2628 self.Layout()
2629
2630
2631
2633 self.SetMinSize((10, 10))
2634
2635 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
2636
2637 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
2638 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
2639
2640 item = self.__action_button_popup.Append(-1, _('Plot'))
2641 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
2642
2643 item = self.__action_button_popup.Append(-1, _('Export to &file'))
2644 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item)
2645 self.__action_button_popup.Enable(id = menu_id, enable = False)
2646
2647 item = self.__action_button_popup.Append(-1, _('Export to &clipboard'))
2648 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item)
2649 self.__action_button_popup.Enable(id = menu_id, enable = False)
2650
2651 item = self.__action_button_popup.Append(-1, _('&Delete'))
2652 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
2653
2654
2655
2656
2657 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
2658 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
2659
2660 self._GRID_results_battery.show_by_panel = True
2661 self._GRID_results_battery.panel_to_show = None
2662
2663 self._PNL_results_battery_grid.Hide()
2664 self._BTN_display_mode.SetLabel(_('All: by &Day'))
2665
2666 self._PNL_results_all_grid.Show()
2667 self._PNL_results_all_listed.Hide()
2668 self.Layout()
2669
2670 self._PRW_panel.SetFocus()
2671
2672
2673
2675 pat = gmPerson.gmCurrentPatient()
2676 if pat.connected:
2677 self._GRID_results_battery.patient = pat
2678 if self.__display_mode == 'grid':
2679 self._GRID_results_all.patient = pat
2680 self._PNL_results_all_listed.patient = None
2681 else:
2682 self._GRID_results_all.patient = None
2683 self._PNL_results_all_listed.patient = pat
2684 else:
2685 self._GRID_results_battery.patient = None
2686 self._GRID_results_all.patient = None
2687 self._PNL_results_all_listed.patient = None
2688 return True
2689
2690
2691
2692
2694
2695 if tests is None:
2696 return True
2697
2698 if len(tests) == 0:
2699 return True
2700
2701 if parent is None:
2702 parent = wx.GetApp().GetTopWindow()
2703
2704 if len(tests) > 10:
2705 test_count = len(tests)
2706 tests2show = None
2707 else:
2708 test_count = None
2709 tests2show = tests
2710 if len(tests) == 0:
2711 return True
2712
2713 dlg = cMeasurementsReviewDlg(parent, -1, tests = tests, test_count = test_count)
2714 decision = dlg.ShowModal()
2715 if decision != wx.ID_APPLY:
2716 return True
2717
2718 wx.BeginBusyCursor()
2719 if dlg._RBTN_confirm_abnormal.GetValue():
2720 abnormal = None
2721 elif dlg._RBTN_results_normal.GetValue():
2722 abnormal = False
2723 else:
2724 abnormal = True
2725
2726 if dlg._RBTN_confirm_relevance.GetValue():
2727 relevant = None
2728 elif dlg._RBTN_results_not_relevant.GetValue():
2729 relevant = False
2730 else:
2731 relevant = True
2732
2733 comment = None
2734 if len(tests) == 1:
2735 comment = dlg._TCTRL_comment.GetValue()
2736
2737 make_responsible = dlg._CHBOX_responsible.IsChecked()
2738 dlg.DestroyLater()
2739
2740 for test in tests:
2741 test.set_review (
2742 technically_abnormal = abnormal,
2743 clinically_relevant = relevant,
2744 comment = comment,
2745 make_me_responsible = make_responsible
2746 )
2747 wx.EndBusyCursor()
2748
2749 return True
2750
2751
2752 from Gnumed.wxGladeWidgets import wxgMeasurementsReviewDlg
2753
2755
2757
2758 try:
2759 tests = kwargs['tests']
2760 del kwargs['tests']
2761 test_count = len(tests)
2762 try: del kwargs['test_count']
2763 except KeyError: pass
2764 except KeyError:
2765 tests = None
2766 test_count = kwargs['test_count']
2767 del kwargs['test_count']
2768
2769 wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs)
2770
2771 if tests is None:
2772 msg = _('%s results selected. Too many to list individually.') % test_count
2773 else:
2774 msg = '\n'.join (
2775 [ '%s: %s %s (%s)' % (
2776 t['unified_abbrev'],
2777 t['unified_val'],
2778 t['val_unit'],
2779 gmDateTime.pydt_strftime(t['clin_when'], '%Y %b %d')
2780 ) for t in tests
2781 ]
2782 )
2783
2784 self._LBL_tests.SetLabel(msg)
2785
2786 if test_count == 1:
2787 self._TCTRL_comment.Enable(True)
2788 self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], ''))
2789 if tests[0]['you_are_responsible']:
2790 self._CHBOX_responsible.Enable(False)
2791
2792 self.Fit()
2793
2794
2795
2801
2802
2803 from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl
2804
2805 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
2806 """This edit area saves *new* measurements into the active patient only."""
2807
2824
2825
2826
2827
2829 try:
2830 self._PRW_test.SetData(data = fields['pk_test_type']['data'])
2831 except KeyError:
2832 pass
2833 try:
2834 self._DPRW_evaluated.SetData(data = fields['clin_when']['data'])
2835 except KeyError:
2836 pass
2837 try:
2838 self._PRW_problem.SetData(data = fields['pk_episode']['data'])
2839 except KeyError:
2840 pass
2841 try:
2842 self._PRW_units.SetText(fields['val_unit']['data'], fields['val_unit']['data'], True)
2843 except KeyError:
2844 pass
2845 try:
2846 self._TCTRL_normal_min.SetValue(fields['val_normal_min']['data'])
2847 except KeyError:
2848 pass
2849 try:
2850 self._TCTRL_normal_max.SetValue(fields['val_normal_max']['data'])
2851 except KeyError:
2852 pass
2853 try:
2854 self._TCTRL_normal_range.SetValue(fields['val_normal_range']['data'])
2855 except KeyError:
2856 pass
2857 try:
2858 self._TCTRL_target_min.SetValue(fields['val_target_min']['data'])
2859 except KeyError:
2860 pass
2861 try:
2862 self._TCTRL_target_max.SetValue(fields['val_target_max']['data'])
2863 except KeyError:
2864 pass
2865 try:
2866 self._TCTRL_target_range.SetValue(fields['val_target_range']['data'])
2867 except KeyError:
2868 pass
2869
2870 self._TCTRL_result.SetFocus()
2871
2872
2904
2906 self._PRW_test.SetData(data = self.data['pk_test_type'])
2907 self.__refresh_loinc_info()
2908 self.__refresh_previous_value()
2909 self.__update_units_context()
2910 self._TCTRL_result.SetValue(self.data['unified_val'])
2911 self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True)
2912 self._PRW_abnormality_indicator.SetText (
2913 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2914 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2915 True
2916 )
2917 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2918 self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], ''))
2919 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2920 self._PRW_problem.SetData(self.data['pk_episode'])
2921 self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], ''))
2922 self._CHBOX_review.SetValue(False)
2923 self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False))
2924 self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False))
2925 self._CHBOX_abnormal.Enable(False)
2926 self._CHBOX_relevant.Enable(False)
2927 self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], ''))
2928 self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(self.data['val_normal_min'], '')))
2929 self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(self.data['val_normal_max'], '')))
2930 self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], ''))
2931 self._TCTRL_target_min.SetValue(str(gmTools.coalesce(self.data['val_target_min'], '')))
2932 self._TCTRL_target_max.SetValue(str(gmTools.coalesce(self.data['val_target_max'], '')))
2933 self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], ''))
2934 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], ''))
2935
2936 self._TCTRL_result.SetFocus()
2937
2939 self._PRW_test.SetText('', None, True)
2940 self.__refresh_loinc_info()
2941 self.__refresh_previous_value()
2942 self.__update_units_context()
2943 self._TCTRL_result.SetValue('')
2944 self._PRW_units.SetText('', None, True)
2945 self._PRW_abnormality_indicator.SetText('', None, True)
2946 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2947 self._TCTRL_note_test_org.SetValue('')
2948 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2949 self._PRW_problem.SetData(self.data['pk_episode'])
2950 self._TCTRL_narrative.SetValue('')
2951 self._CHBOX_review.SetValue(False)
2952 self._CHBOX_abnormal.SetValue(False)
2953 self._CHBOX_relevant.SetValue(False)
2954 self._CHBOX_abnormal.Enable(False)
2955 self._CHBOX_relevant.Enable(False)
2956 self._TCTRL_review_comment.SetValue('')
2957 self._TCTRL_normal_min.SetValue('')
2958 self._TCTRL_normal_max.SetValue('')
2959 self._TCTRL_normal_range.SetValue('')
2960 self._TCTRL_target_min.SetValue('')
2961 self._TCTRL_target_max.SetValue('')
2962 self._TCTRL_target_range.SetValue('')
2963 self._TCTRL_norm_ref_group.SetValue('')
2964
2965 self._PRW_test.SetFocus()
2966
2968
2969 validity = True
2970
2971 if not self._DPRW_evaluated.is_valid_timestamp():
2972 self._DPRW_evaluated.display_as_valid(False)
2973 validity = False
2974 else:
2975 self._DPRW_evaluated.display_as_valid(True)
2976
2977 val = self._TCTRL_result.GetValue().strip()
2978 if val == '':
2979 validity = False
2980 self.display_ctrl_as_valid(self._TCTRL_result, False)
2981 else:
2982 self.display_ctrl_as_valid(self._TCTRL_result, True)
2983 numeric, val = gmTools.input2decimal(val)
2984 if numeric:
2985 if self._PRW_units.GetValue().strip() == '':
2986 self._PRW_units.display_as_valid(False)
2987 validity = False
2988 else:
2989 self._PRW_units.display_as_valid(True)
2990 else:
2991 self._PRW_units.display_as_valid(True)
2992
2993 if self._PRW_problem.GetValue().strip() == '':
2994 self._PRW_problem.display_as_valid(False)
2995 validity = False
2996 else:
2997 self._PRW_problem.display_as_valid(True)
2998
2999 if self._PRW_test.GetValue().strip() == '':
3000 self._PRW_test.display_as_valid(False)
3001 validity = False
3002 else:
3003 self._PRW_test.display_as_valid(True)
3004
3005 if self._PRW_intended_reviewer.GetData() is None:
3006 self._PRW_intended_reviewer.display_as_valid(False)
3007 validity = False
3008 else:
3009 self._PRW_intended_reviewer.display_as_valid(True)
3010
3011 ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max]
3012 for widget in ctrls:
3013 val = widget.GetValue().strip()
3014 if val == '':
3015 continue
3016 try:
3017 decimal.Decimal(val.replace(',', '.', 1))
3018 self.display_ctrl_as_valid(widget, True)
3019 except Exception:
3020 validity = False
3021 self.display_ctrl_as_valid(widget, False)
3022
3023 if validity is False:
3024 self.StatusText = _('Cannot save result. Invalid or missing essential input.')
3025
3026 return validity
3027
3029
3030 emr = gmPerson.gmCurrentPatient().emr
3031
3032 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
3033 if success:
3034 v_num = result
3035 v_al = None
3036 else:
3037 v_al = self._TCTRL_result.GetValue().strip()
3038 v_num = None
3039
3040 pk_type = self._PRW_test.GetData()
3041 if pk_type is None:
3042 abbrev = self._PRW_test.GetValue().strip()
3043 name = self._PRW_test.GetValue().strip()
3044 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3045 lab = manage_measurement_orgs (
3046 parent = self,
3047 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
3048 )
3049 if lab is not None:
3050 lab = lab['pk_test_org']
3051 tt = gmPathLab.create_measurement_type (
3052 lab = lab,
3053 abbrev = abbrev,
3054 name = name,
3055 unit = unit
3056 )
3057 pk_type = tt['pk_test_type']
3058
3059 tr = emr.add_test_result (
3060 episode = self._PRW_problem.GetData(can_create=True, is_open=False),
3061 type = pk_type,
3062 intended_reviewer = self._PRW_intended_reviewer.GetData(),
3063 val_num = v_num,
3064 val_alpha = v_al,
3065 unit = self._PRW_units.GetValue()
3066 )
3067
3068 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
3069
3070 ctrls = [
3071 ('abnormality_indicator', self._PRW_abnormality_indicator),
3072 ('note_test_org', self._TCTRL_note_test_org),
3073 ('comment', self._TCTRL_narrative),
3074 ('val_normal_range', self._TCTRL_normal_range),
3075 ('val_target_range', self._TCTRL_target_range),
3076 ('norm_ref_group', self._TCTRL_norm_ref_group)
3077 ]
3078 for field, widget in ctrls:
3079 tr[field] = widget.GetValue().strip()
3080
3081 ctrls = [
3082 ('val_normal_min', self._TCTRL_normal_min),
3083 ('val_normal_max', self._TCTRL_normal_max),
3084 ('val_target_min', self._TCTRL_target_min),
3085 ('val_target_max', self._TCTRL_target_max)
3086 ]
3087 for field, widget in ctrls:
3088 val = widget.GetValue().strip()
3089 if val == '':
3090 tr[field] = None
3091 else:
3092 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
3093
3094 tr.save_payload()
3095
3096 if self._CHBOX_review.GetValue() is True:
3097 tr.set_review (
3098 technically_abnormal = self._CHBOX_abnormal.GetValue(),
3099 clinically_relevant = self._CHBOX_relevant.GetValue(),
3100 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
3101 make_me_responsible = False
3102 )
3103
3104 self.data = tr
3105
3106
3107
3108
3109
3110
3111
3112
3113 return True
3114
3116
3117 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
3118 if success:
3119 v_num = result
3120 v_al = None
3121 else:
3122 v_num = None
3123 v_al = self._TCTRL_result.GetValue().strip()
3124
3125 pk_type = self._PRW_test.GetData()
3126 if pk_type is None:
3127 abbrev = self._PRW_test.GetValue().strip()
3128 name = self._PRW_test.GetValue().strip()
3129 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3130 lab = manage_measurement_orgs (
3131 parent = self,
3132 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
3133 )
3134 if lab is not None:
3135 lab = lab['pk_test_org']
3136 tt = gmPathLab.create_measurement_type (
3137 lab = None,
3138 abbrev = abbrev,
3139 name = name,
3140 unit = unit
3141 )
3142 pk_type = tt['pk_test_type']
3143
3144 tr = self.data
3145
3146 tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False)
3147 tr['pk_test_type'] = pk_type
3148 tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData()
3149 tr['val_num'] = v_num
3150 tr['val_alpha'] = v_al
3151 tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3152 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
3153
3154 ctrls = [
3155 ('abnormality_indicator', self._PRW_abnormality_indicator),
3156 ('note_test_org', self._TCTRL_note_test_org),
3157 ('comment', self._TCTRL_narrative),
3158 ('val_normal_range', self._TCTRL_normal_range),
3159 ('val_target_range', self._TCTRL_target_range),
3160 ('norm_ref_group', self._TCTRL_norm_ref_group)
3161 ]
3162 for field, widget in ctrls:
3163 tr[field] = widget.GetValue().strip()
3164
3165 ctrls = [
3166 ('val_normal_min', self._TCTRL_normal_min),
3167 ('val_normal_max', self._TCTRL_normal_max),
3168 ('val_target_min', self._TCTRL_target_min),
3169 ('val_target_max', self._TCTRL_target_max)
3170 ]
3171 for field, widget in ctrls:
3172 val = widget.GetValue().strip()
3173 if val == '':
3174 tr[field] = None
3175 else:
3176 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
3177
3178 tr.save_payload()
3179
3180 if self._CHBOX_review.GetValue() is True:
3181 tr.set_review (
3182 technically_abnormal = self._CHBOX_abnormal.GetValue(),
3183 clinically_relevant = self._CHBOX_relevant.GetValue(),
3184 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
3185 make_me_responsible = False
3186 )
3187
3188
3189
3190
3191
3192
3193
3194
3195 return True
3196
3197
3198
3203
3205 self.__refresh_loinc_info()
3206 self.__refresh_previous_value()
3207 self.__update_units_context()
3208
3209 self.__update_normal_range()
3210 self.__update_clinical_range()
3211
3213
3214 self.__update_normal_range()
3215 self.__update_clinical_range()
3216
3218
3219 if not self._CHBOX_review.GetValue():
3220 self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != '')
3221
3226
3242
3246
3247
3248
3250
3251 if self._PRW_test.GetData() is None:
3252 self._PRW_units.unset_context(context = 'pk_type')
3253 self._PRW_units.unset_context(context = 'loinc')
3254 if self._PRW_test.GetValue().strip() == '':
3255 self._PRW_units.unset_context(context = 'test_name')
3256 else:
3257 self._PRW_units.set_context(context = 'test_name', val = self._PRW_test.GetValue().strip())
3258 return
3259
3260 tt = self._PRW_test.GetData(as_instance = True)
3261
3262 self._PRW_units.set_context(context = 'pk_type', val = tt['pk_test_type'])
3263 self._PRW_units.set_context(context = 'test_name', val = tt['name'])
3264
3265 if tt['loinc'] is not None:
3266 self._PRW_units.set_context(context = 'loinc', val = tt['loinc'])
3267
3268
3269 if self._PRW_units.GetValue().strip() == '':
3270 clin_when = self._DPRW_evaluated.GetData()
3271 if clin_when is None:
3272 unit = tt.temporally_closest_unit
3273 else:
3274 clin_when = clin_when.get_pydt()
3275 unit = tt.get_temporally_closest_unit(timestamp = clin_when)
3276 if unit is None:
3277 self._PRW_units.SetText('', unit, True)
3278 else:
3279 self._PRW_units.SetText(unit, unit, True)
3280
3281
3302
3303
3324
3325
3327
3328 self._TCTRL_loinc.SetValue('')
3329
3330 if self._PRW_test.GetData() is None:
3331 return
3332
3333 tt = self._PRW_test.GetData(as_instance = True)
3334
3335 if tt['loinc'] is None:
3336 return
3337
3338 info = gmLOINC.loinc2term(loinc = tt['loinc'])
3339 if len(info) == 0:
3340 self._TCTRL_loinc.SetValue('')
3341 return
3342
3343 self._TCTRL_loinc.SetValue('%s: %s' % (tt['loinc'], info[0]))
3344
3345
3347 self._TCTRL_previous_value.SetValue('')
3348
3349
3350 if self.data is not None:
3351 return
3352
3353 if self._PRW_test.GetData() is None:
3354 return
3355
3356 tt = self._PRW_test.GetData(as_instance = True)
3357 most_recent_results = tt.get_most_recent_results (
3358 max_no_of_results = 1,
3359 patient = gmPerson.gmCurrentPatient().ID
3360 )
3361 if len(most_recent_results) == 0:
3362 return
3363
3364 most_recent = most_recent_results[0]
3365 self._TCTRL_previous_value.SetValue(_('%s ago: %s%s%s - %s%s') % (
3366 gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - most_recent['clin_when']),
3367 most_recent['unified_val'],
3368 most_recent['val_unit'],
3369 gmTools.coalesce(most_recent['abnormality_indicator'], '', ' (%s)'),
3370 most_recent['abbrev_tt'],
3371 gmTools.coalesce(most_recent.formatted_range, '', ' [%s]')
3372 ))
3373 self._TCTRL_previous_value.SetToolTip(most_recent.format (
3374 with_review = True,
3375 with_evaluation = False,
3376 with_ranges = True,
3377 with_episode = True,
3378 with_type_details=True
3379 ))
3380
3381
3382
3383
3385
3386 if parent is None:
3387 parent = wx.GetApp().GetTopWindow()
3388
3389 if msg is None:
3390 msg = _('Pick the relevant measurement types.')
3391
3392 if right_column is None:
3393 right_columns = [_('Picked')]
3394 else:
3395 right_columns = [right_column]
3396
3397 picker = gmListWidgets.cItemPickerDlg(parent, -1, msg = msg)
3398 picker.set_columns(columns = [_('Known measurement types')], columns_right = right_columns)
3399 types = gmPathLab.get_measurement_types(order_by = 'unified_abbrev')
3400 picker.set_choices (
3401 choices = [
3402 '%s: %s%s' % (
3403 t['unified_abbrev'],
3404 t['unified_name'],
3405 gmTools.coalesce(t['name_org'], '', ' (%s)')
3406 )
3407 for t in types
3408 ],
3409 data = types
3410 )
3411 if picks is not None:
3412 picker.set_picks (
3413 picks = [
3414 '%s: %s%s' % (
3415 p['unified_abbrev'],
3416 p['unified_name'],
3417 gmTools.coalesce(p['name_org'], '', ' (%s)')
3418 )
3419 for p in picks
3420 ],
3421 data = picks
3422 )
3423 result = picker.ShowModal()
3424
3425 if result == wx.ID_CANCEL:
3426 picker.DestroyLater()
3427 return None
3428
3429 picks = picker.picks
3430 picker.DestroyLater()
3431 return picks
3432
3433
3456
3457
3458 def delete(measurement_type):
3459 if measurement_type.in_use:
3460 gmDispatcher.send (
3461 signal = 'statustext',
3462 beep = True,
3463 msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev'])
3464 )
3465 return False
3466 gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type'])
3467 return True
3468
3469
3470 def get_tooltip(test_type):
3471 return test_type.format()
3472
3473
3474 def manage_aggregates(test_type):
3475 manage_meta_test_types(parent = parent)
3476 return False
3477
3478
3479 def manage_panels_of_type(test_type):
3480 if test_type['loinc'] is None:
3481 return False
3482 all_panels = gmPathLab.get_test_panels(order_by = 'description')
3483 curr_panels = test_type.test_panels
3484 if curr_panels is None:
3485 curr_panels = []
3486 panel_candidates = [ p for p in all_panels if p['pk_test_panel'] not in [
3487 c_pnl['pk_test_panel'] for c_pnl in curr_panels
3488 ] ]
3489 picker = gmListWidgets.cItemPickerDlg(parent, -1, title = 'Panels with [%s]' % test_type['abbrev'])
3490 picker.set_columns(['Panels available'], ['Panels [%s] is to be on' % test_type['abbrev']])
3491 picker.set_choices (
3492 choices = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in panel_candidates ],
3493 data = panel_candidates
3494 )
3495 picker.set_picks (
3496 picks = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in curr_panels ],
3497 data = curr_panels
3498 )
3499 exit_type = picker.ShowModal()
3500 if exit_type == wx.ID_CANCEL:
3501 return False
3502
3503
3504 panels2add = [ p for p in picker.picks if p['pk_test_panel'] not in [
3505 c_pnl['pk_test_panel'] for c_pnl in curr_panels
3506 ] ]
3507
3508 panels2remove = [ p for p in curr_panels if p['pk_test_panel'] not in [
3509 picked_pnl['pk_test_panel'] for picked_pnl in picker.picks
3510 ] ]
3511 for new_panel in panels2add:
3512 new_panel.add_loinc(test_type['loinc'])
3513 for stale_panel in panels2remove:
3514 stale_panel.remove_loinc(test_type['loinc'])
3515
3516 return True
3517
3518
3519 def refresh(lctrl):
3520 mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev')
3521 items = [ [
3522 m['abbrev'],
3523 m['name'],
3524 gmTools.coalesce(m['reference_unit'], ''),
3525 gmTools.coalesce(m['loinc'], ''),
3526 gmTools.coalesce(m['comment_type'], ''),
3527 gmTools.coalesce(m['name_org'], '?'),
3528 gmTools.coalesce(m['comment_org'], ''),
3529 m['pk_test_type']
3530 ] for m in mtypes ]
3531 lctrl.set_string_items(items)
3532 lctrl.set_data(mtypes)
3533
3534
3535 gmListWidgets.get_choices_from_list (
3536 parent = parent,
3537 caption = _('Measurement types.'),
3538 columns = [ _('Abbrev'), _('Name'), _('Unit'), _('LOINC'), _('Comment'), _('Org'), _('Comment'), '#' ],
3539 single_selection = True,
3540 refresh_callback = refresh,
3541 edit_callback = edit,
3542 new_callback = edit,
3543 delete_callback = delete,
3544 list_tooltip_callback = get_tooltip,
3545 left_extra_button = (_('%s &Aggregate') % gmTools.u_sum, _('Manage aggregations (%s) of tests into groups.') % gmTools.u_sum, manage_aggregates),
3546 middle_extra_button = (_('Select panels'), _('Select panels the focussed test type is to belong to.'), manage_panels_of_type)
3547 )
3548
3549
3551
3553
3554 query = """
3555 SELECT DISTINCT ON (field_label)
3556 pk_test_type AS data,
3557 name
3558 || ' ('
3559 || coalesce (
3560 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
3561 '%(in_house)s'
3562 )
3563 || ')'
3564 AS field_label,
3565 name
3566 || ' ('
3567 || abbrev || ', '
3568 || coalesce(abbrev_meta || ': ' || name_meta || ', ', '')
3569 || coalesce (
3570 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
3571 '%(in_house)s'
3572 )
3573 || ')'
3574 AS list_label
3575 FROM
3576 clin.v_test_types c_vtt
3577 WHERE
3578 abbrev_meta %%(fragment_condition)s
3579 OR
3580 name_meta %%(fragment_condition)s
3581 OR
3582 abbrev %%(fragment_condition)s
3583 OR
3584 name %%(fragment_condition)s
3585 ORDER BY field_label
3586 LIMIT 50""" % {'in_house': _('generic / in house lab')}
3587
3588 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3589 mp.setThresholds(1, 2, 4)
3590 mp.word_separators = '[ \t:@]+'
3591 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
3592 self.matcher = mp
3593 self.SetToolTip(_('Select the type of measurement.'))
3594 self.selection_only = False
3595
3596
3602
3603
3605 lab = gmPathLab.cTestOrg(aPK_obj = instance['pk_test_org'])
3606 field_label = '%s (%s @ %s)' % (
3607 instance['name'],
3608 lab['unit'],
3609 lab['organization']
3610 )
3611 return self.SetText(value = field_label, data = instance['pk_test_type'])
3612
3613
3616
3617
3620
3621
3622 from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl
3623
3624 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
3625
3642
3643
3645
3646
3647 query = """
3648 select distinct on (name)
3649 pk,
3650 name
3651 from clin.test_type
3652 where
3653 name %(fragment_condition)s
3654 order by name
3655 limit 50"""
3656 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3657 mp.setThresholds(1, 2, 4)
3658 self._PRW_name.matcher = mp
3659 self._PRW_name.selection_only = False
3660 self._PRW_name.add_callback_on_lose_focus(callback = self._on_name_lost_focus)
3661
3662
3663 query = """
3664 select distinct on (abbrev)
3665 pk,
3666 abbrev
3667 from clin.test_type
3668 where
3669 abbrev %(fragment_condition)s
3670 order by abbrev
3671 limit 50"""
3672 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3673 mp.setThresholds(1, 2, 3)
3674 self._PRW_abbrev.matcher = mp
3675 self._PRW_abbrev.selection_only = False
3676
3677
3678 self._PRW_reference_unit.selection_only = False
3679
3680
3681 mp = gmLOINC.cLOINCMatchProvider()
3682 mp.setThresholds(1, 2, 4)
3683
3684
3685 self._PRW_loinc.matcher = mp
3686 self._PRW_loinc.selection_only = False
3687 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
3688
3689
3691
3692 test = self._PRW_name.GetValue().strip()
3693
3694 if test == '':
3695 self._PRW_reference_unit.unset_context(context = 'test_name')
3696 return
3697
3698 self._PRW_reference_unit.set_context(context = 'test_name', val = test)
3699
3700
3702 loinc = self._PRW_loinc.GetData()
3703
3704 if loinc is None:
3705 self._TCTRL_loinc_info.SetValue('')
3706 self._PRW_reference_unit.unset_context(context = 'loinc')
3707 return
3708
3709 self._PRW_reference_unit.set_context(context = 'loinc', val = loinc)
3710
3711 info = gmLOINC.loinc2term(loinc = loinc)
3712 if len(info) == 0:
3713 self._TCTRL_loinc_info.SetValue('')
3714 return
3715
3716 self._TCTRL_loinc_info.SetValue(info[0])
3717
3718
3719
3720
3722
3723 has_errors = False
3724 for field in [self._PRW_name, self._PRW_abbrev, self._PRW_reference_unit]:
3725 if field.GetValue().strip() in ['', None]:
3726 has_errors = True
3727 field.display_as_valid(valid = False)
3728 else:
3729 field.display_as_valid(valid = True)
3730 field.Refresh()
3731
3732 return (not has_errors)
3733
3734
3764
3803
3804
3806 self._PRW_name.SetText('', None, True)
3807 self._on_name_lost_focus()
3808 self._PRW_abbrev.SetText('', None, True)
3809 self._PRW_reference_unit.SetText('', None, True)
3810 self._PRW_loinc.SetText('', None, True)
3811 self._on_loinc_lost_focus()
3812 self._TCTRL_comment_type.SetValue('')
3813 self._PRW_test_org.SetText('', None, True)
3814 self._PRW_meta_type.SetText('', None, True)
3815
3816 self._PRW_name.SetFocus()
3817
3819 self._PRW_name.SetText(self.data['name'], self.data['name'], True)
3820 self._on_name_lost_focus()
3821 self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True)
3822 self._PRW_reference_unit.SetText (
3823 gmTools.coalesce(self.data['reference_unit'], ''),
3824 self.data['reference_unit'],
3825 True
3826 )
3827 self._PRW_loinc.SetText (
3828 gmTools.coalesce(self.data['loinc'], ''),
3829 self.data['loinc'],
3830 True
3831 )
3832 self._on_loinc_lost_focus()
3833 self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], ''))
3834 self._PRW_test_org.SetText (
3835 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
3836 self.data['pk_test_org'],
3837 True
3838 )
3839 if self.data['pk_meta_test_type'] is None:
3840 self._PRW_meta_type.SetText('', None, True)
3841 else:
3842 self._PRW_meta_type.SetText('%s: %s' % (self.data['abbrev_meta'], self.data['name_meta']), self.data['pk_meta_test_type'], True)
3843
3844 self._PRW_name.SetFocus()
3845
3847 self._refresh_as_new()
3848 self._PRW_test_org.SetText (
3849 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
3850 self.data['pk_test_org'],
3851 True
3852 )
3853 self._PRW_name.SetFocus()
3854
3855
3856 _SQL_units_from_test_results = """
3857 -- via clin.v_test_results.pk_type (for types already used in results)
3858 SELECT
3859 val_unit AS data,
3860 val_unit AS field_label,
3861 val_unit || ' (' || name_tt || ')' AS list_label,
3862 1 AS rank
3863 FROM
3864 clin.v_test_results
3865 WHERE
3866 (
3867 val_unit %(fragment_condition)s
3868 OR
3869 reference_unit %(fragment_condition)s
3870 )
3871 %(ctxt_type_pk)s
3872 %(ctxt_test_name)s
3873 """
3874
3875 _SQL_units_from_test_types = """
3876 -- via clin.test_type (for types not yet used in results)
3877 SELECT
3878 reference_unit AS data,
3879 reference_unit AS field_label,
3880 reference_unit || ' (' || name || ')' AS list_label,
3881 2 AS rank
3882 FROM
3883 clin.test_type
3884 WHERE
3885 reference_unit %(fragment_condition)s
3886 %(ctxt_ctt)s
3887 """
3888
3889 _SQL_units_from_loinc_ipcc = """
3890 -- via ref.loinc.ipcc_units
3891 SELECT
3892 ipcc_units AS data,
3893 ipcc_units AS field_label,
3894 ipcc_units || ' (LOINC.ipcc: ' || term || ')' AS list_label,
3895 3 AS rank
3896 FROM
3897 ref.loinc
3898 WHERE
3899 ipcc_units %(fragment_condition)s
3900 %(ctxt_loinc)s
3901 %(ctxt_loinc_term)s
3902 """
3903
3904 _SQL_units_from_loinc_submitted = """
3905 -- via ref.loinc.submitted_units
3906 SELECT
3907 submitted_units AS data,
3908 submitted_units AS field_label,
3909 submitted_units || ' (LOINC.submitted:' || term || ')' AS list_label,
3910 3 AS rank
3911 FROM
3912 ref.loinc
3913 WHERE
3914 submitted_units %(fragment_condition)s
3915 %(ctxt_loinc)s
3916 %(ctxt_loinc_term)s
3917 """
3918
3919 _SQL_units_from_loinc_example = """
3920 -- via ref.loinc.example_units
3921 SELECT
3922 example_units AS data,
3923 example_units AS field_label,
3924 example_units || ' (LOINC.example: ' || term || ')' AS list_label,
3925 3 AS rank
3926 FROM
3927 ref.loinc
3928 WHERE
3929 example_units %(fragment_condition)s
3930 %(ctxt_loinc)s
3931 %(ctxt_loinc_term)s
3932 """
3933
3934 _SQL_units_from_substance_doses = """
3935 -- via ref.v_substance_doses.unit
3936 SELECT
3937 unit AS data,
3938 unit AS field_label,
3939 unit || ' (' || substance || ')' AS list_label,
3940 2 AS rank
3941 FROM
3942 ref.v_substance_doses
3943 WHERE
3944 unit %(fragment_condition)s
3945 %(ctxt_substance)s
3946 """
3947
3948 _SQL_units_from_substance_doses2 = """
3949 -- via ref.v_substance_doses.dose_unit
3950 SELECT
3951 dose_unit AS data,
3952 dose_unit AS field_label,
3953 dose_unit || ' (' || substance || ')' AS list_label,
3954 2 AS rank
3955 FROM
3956 ref.v_substance_doses
3957 WHERE
3958 dose_unit %(fragment_condition)s
3959 %(ctxt_substance)s
3960 """
3961
3962
3964
3966
3967 query = """
3968 SELECT DISTINCT ON (data)
3969 data,
3970 field_label,
3971 list_label
3972 FROM (
3973
3974 SELECT
3975 data,
3976 field_label,
3977 list_label,
3978 rank
3979 FROM (
3980 (%s) UNION ALL
3981 (%s) UNION ALL
3982 (%s) UNION ALL
3983 (%s) UNION ALL
3984 (%s) UNION ALL
3985 (%s) UNION ALL
3986 (%s)
3987 ) AS all_matching_units
3988 WHERE data IS NOT NULL
3989 ORDER BY rank, list_label
3990
3991 ) AS ranked_matching_units
3992 LIMIT 50""" % (
3993 _SQL_units_from_test_results,
3994 _SQL_units_from_test_types,
3995 _SQL_units_from_loinc_ipcc,
3996 _SQL_units_from_loinc_submitted,
3997 _SQL_units_from_loinc_example,
3998 _SQL_units_from_substance_doses,
3999 _SQL_units_from_substance_doses2
4000 )
4001
4002 ctxt = {
4003 'ctxt_type_pk': {
4004 'where_part': 'AND pk_test_type = %(pk_type)s',
4005 'placeholder': 'pk_type'
4006 },
4007 'ctxt_test_name': {
4008 'where_part': 'AND %(test_name)s IN (name_tt, name_meta, abbrev_meta)',
4009 'placeholder': 'test_name'
4010 },
4011 'ctxt_ctt': {
4012 'where_part': 'AND %(test_name)s IN (name, abbrev)',
4013 'placeholder': 'test_name'
4014 },
4015 'ctxt_loinc': {
4016 'where_part': 'AND code = %(loinc)s',
4017 'placeholder': 'loinc'
4018 },
4019 'ctxt_loinc_term': {
4020 'where_part': 'AND term ~* %(test_name)s',
4021 'placeholder': 'test_name'
4022 },
4023 'ctxt_substance': {
4024 'where_part': 'AND description ~* %(substance)s',
4025 'placeholder': 'substance'
4026 }
4027 }
4028
4029 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = ctxt)
4030 mp.setThresholds(1, 2, 4)
4031
4032 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4033 self.matcher = mp
4034 self.SetToolTip(_('Select the desired unit for the amount or measurement.'))
4035 self.selection_only = False
4036 self.phrase_separators = '[;|]+'
4037
4038
4039
4040
4042
4044
4045 query = """
4046 select distinct abnormality_indicator,
4047 abnormality_indicator, abnormality_indicator
4048 from clin.v_test_results
4049 where
4050 abnormality_indicator %(fragment_condition)s
4051 order by abnormality_indicator
4052 limit 25"""
4053
4054 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4055 mp.setThresholds(1, 1, 2)
4056 mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"'
4057 mp.word_separators = '[ \t&:]+'
4058 gmPhraseWheel.cPhraseWheel.__init__ (
4059 self,
4060 *args,
4061 **kwargs
4062 )
4063 self.matcher = mp
4064 self.SetToolTip(_('Select an indicator for the level of abnormality.'))
4065 self.selection_only = False
4066
4067
4068
4069
4081
4090
4091 def refresh(lctrl):
4092 orgs = gmPathLab.get_test_orgs()
4093 lctrl.set_string_items ([
4094 (o['unit'], o['organization'], gmTools.coalesce(o['test_org_contact'], ''), gmTools.coalesce(o['comment'], ''), o['pk_test_org'])
4095 for o in orgs
4096 ])
4097 lctrl.set_data(orgs)
4098
4099 def delete(test_org):
4100 gmPathLab.delete_test_org(test_org = test_org['pk_test_org'])
4101 return True
4102
4103 if msg is None:
4104 msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n')
4105
4106 return gmListWidgets.get_choices_from_list (
4107 parent = parent,
4108 msg = msg,
4109 caption = _('Showing diagnostic orgs.'),
4110 columns = [_('Name'), _('Organization'), _('Contact'), _('Comment'), '#'],
4111 single_selection = True,
4112 refresh_callback = refresh,
4113 edit_callback = edit,
4114 new_callback = edit,
4115 delete_callback = delete
4116 )
4117
4118
4119 from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl
4120
4121 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
4122
4138
4139
4140
4141
4142
4143
4144
4145
4147 has_errors = False
4148 if self._PRW_org_unit.GetData() is None:
4149 if self._PRW_org_unit.GetValue().strip() == '':
4150 has_errors = True
4151 self._PRW_org_unit.display_as_valid(valid = False)
4152 else:
4153 self._PRW_org_unit.display_as_valid(valid = True)
4154 else:
4155 self._PRW_org_unit.display_as_valid(valid = True)
4156
4157 return (not has_errors)
4158
4169
4189
4194
4199
4201 self._refresh_as_new()
4202
4205
4206
4208
4210
4211 query = """
4212 SELECT DISTINCT ON (list_label)
4213 pk_test_org AS data,
4214 unit || ' (' || organization || ')' AS field_label,
4215 unit || ' @ ' || organization AS list_label
4216 FROM clin.v_test_orgs
4217 WHERE
4218 unit %(fragment_condition)s
4219 OR
4220 organization %(fragment_condition)s
4221 ORDER BY list_label
4222 LIMIT 50"""
4223 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4224 mp.setThresholds(1, 2, 4)
4225
4226 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4227 self.matcher = mp
4228 self.SetToolTip(_('The name of the path lab/diagnostic organisation.'))
4229 self.selection_only = False
4230
4243
4246
4247
4248
4249
4266
4267
4276
4277 def delete(meta_test_type):
4278 gmPathLab.delete_meta_type(meta_type = meta_test_type['pk'])
4279 return True
4280
4281 def get_tooltip(data):
4282 if data is None:
4283 return None
4284 return data.format(with_tests = True)
4285
4286 def refresh(lctrl):
4287 mtts = gmPathLab.get_meta_test_types()
4288 items = [ [
4289 m['abbrev'],
4290 m['name'],
4291 gmTools.coalesce(m['loinc'], ''),
4292 gmTools.coalesce(m['comment'], ''),
4293 m['pk']
4294 ] for m in mtts ]
4295 lctrl.set_string_items(items)
4296 lctrl.set_data(mtts)
4297
4298
4299 msg = _(
4300 '\n'
4301 'These are the meta test types currently defined in GNUmed.\n'
4302 '\n'
4303 'Meta test types allow you to aggregate several actual test types used\n'
4304 'by pathology labs into one logical type.\n'
4305 '\n'
4306 'This is useful for grouping together results of tests which come under\n'
4307 'different names but really are the same thing. This often happens when\n'
4308 'you switch labs or the lab starts using another test method.\n'
4309 )
4310
4311 gmListWidgets.get_choices_from_list (
4312 parent = parent,
4313 msg = msg,
4314 caption = _('Showing meta test types.'),
4315 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), '#'],
4316 single_selection = True,
4317 list_tooltip_callback = get_tooltip,
4318 edit_callback = edit,
4319 new_callback = edit,
4320 delete_callback = delete,
4321 refresh_callback = refresh
4322 )
4323
4324
4369
4370
4371 from Gnumed.wxGladeWidgets import wxgMetaTestTypeEAPnl
4372
4519
4520
4521
4522
4524 ea = cTestPanelEAPnl(parent, -1)
4525 ea.data = test_panel
4526 ea.mode = gmTools.coalesce(test_panel, 'new', 'edit')
4527 dlg = gmEditArea.cGenericEditAreaDlg2 (
4528 parent = parent,
4529 id = -1,
4530 edit_area = ea,
4531 single_entry = gmTools.bool2subst((test_panel is None), False, True)
4532 )
4533 dlg.SetTitle(gmTools.coalesce(test_panel, _('Adding new test panel'), _('Editing test panel')))
4534 if dlg.ShowModal() == wx.ID_OK:
4535 dlg.DestroyLater()
4536 return True
4537 dlg.DestroyLater()
4538 return False
4539
4540
4549
4550 def delete(test_panel):
4551 gmPathLab.delete_test_panel(pk = test_panel['pk_test_panel'])
4552 return True
4553
4554 def get_tooltip(test_panel):
4555 return test_panel.format()
4556
4557 def refresh(lctrl):
4558 panels = gmPathLab.get_test_panels(order_by = 'description')
4559 items = [ [
4560 p['description'],
4561 gmTools.coalesce(p['comment'], ''),
4562 p['pk_test_panel']
4563 ] for p in panels ]
4564 lctrl.set_string_items(items)
4565 lctrl.set_data(panels)
4566
4567 gmListWidgets.get_choices_from_list (
4568 parent = parent,
4569 caption = 'GNUmed: ' + _('Test panels list'),
4570 columns = [ _('Name'), _('Comment'), '#' ],
4571 single_selection = True,
4572 refresh_callback = refresh,
4573 edit_callback = edit,
4574 new_callback = edit,
4575 delete_callback = delete,
4576 list_tooltip_callback = get_tooltip
4577 )
4578
4579
4581
4583 query = """
4584 SELECT
4585 pk_test_panel
4586 AS data,
4587 description
4588 AS field_label,
4589 description
4590 AS list_label
4591 FROM
4592 clin.v_test_panels
4593 WHERE
4594 description %(fragment_condition)s
4595 ORDER BY field_label
4596 LIMIT 30"""
4597 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4598 mp.setThresholds(1, 2, 4)
4599
4600 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4601 self.matcher = mp
4602 self.SetToolTip(_('Select a test panel.'))
4603 self.selection_only = True
4604
4609
4614
4615
4616 from Gnumed.wxGladeWidgets import wxgTestPanelEAPnl
4617
4618 -class cTestPanelEAPnl(wxgTestPanelEAPnl.wxgTestPanelEAPnl, gmEditArea.cGenericEditAreaMixin):
4619
4639
4640
4642 self._LCTRL_loincs.set_columns([_('LOINC'), _('Term'), _('Units')])
4643 self._LCTRL_loincs.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
4644
4645 self._LCTRL_loincs.delete_callback = self._remove_loincs_from_list
4646 self.__refresh_loinc_list()
4647
4648 self._PRW_loinc.final_regex = r'.*'
4649 self._PRW_loinc.add_callback_on_selection(callback = self._on_loinc_selected)
4650
4651
4653 self._LCTRL_loincs.remove_items_safely()
4654 if self.__loincs is None:
4655 if self.data is None:
4656 return
4657 self.__loincs = self.data['loincs']
4658
4659 items = []
4660 for loinc in self.__loincs:
4661 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4662 if loinc_detail is None:
4663
4664 ttypes = gmPathLab.get_measurement_types(loincs = [loinc])
4665 if len(ttypes) == 0:
4666 items.append([loinc, _('LOINC not found'), ''])
4667 else:
4668 for tt in ttypes:
4669 items.append([loinc, _('not a LOINC') + u'; %(name)s @ %(name_org)s [#%(pk_test_type)s]' % tt, ''])
4670 continue
4671 items.append ([
4672 loinc,
4673 loinc_detail['term'],
4674 gmTools.coalesce(loinc_detail['example_units'], '', '%s')
4675 ])
4676
4677 self._LCTRL_loincs.set_string_items(items)
4678 self._LCTRL_loincs.set_column_widths()
4679
4680
4681
4682
4684 validity = True
4685
4686 if self.__loincs is None:
4687 if self.data is not None:
4688 self.__loincs = self.data['loincs']
4689
4690 if self.__loincs is None:
4691
4692 self.StatusText = _('No LOINC codes selected.')
4693 self._PRW_loinc.SetFocus()
4694
4695 if self._TCTRL_description.GetValue().strip() == '':
4696 validity = False
4697 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False)
4698 self._TCTRL_description.SetFocus()
4699 else:
4700 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True)
4701
4702 return validity
4703
4704
4713
4714
4716 self.data['description'] = self._TCTRL_description.GetValue().strip()
4717 self.data['comment'] = self._TCTRL_comment.GetValue().strip()
4718 self.data.save()
4719 if self.__loincs is not None:
4720 self.data.included_loincs = self.__loincs
4721 return True
4722
4723
4725 self._TCTRL_description.SetValue('')
4726 self._TCTRL_comment.SetValue('')
4727 self._PRW_loinc.SetText('', None)
4728 self._LBL_loinc.SetLabel('')
4729 self.__loincs = None
4730 self.__refresh_loinc_list()
4731
4732 self._TCTRL_description.SetFocus()
4733
4734
4736 self._refresh_as_new()
4737
4738
4740 self._TCTRL_description.SetValue(self.data['description'])
4741 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
4742 self._PRW_loinc.SetText('', None)
4743 self._LBL_loinc.SetLabel('')
4744 self.__loincs = self.data['loincs']
4745 self.__refresh_loinc_list()
4746
4747 self._PRW_loinc.SetFocus()
4748
4749
4750
4751
4753 loinc = self._PRW_loinc.GetData()
4754 if loinc is None:
4755 self._LBL_loinc.SetLabel('')
4756 return
4757 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4758 if loinc_detail is None:
4759 loinc_str = _('no LOINC details found')
4760 else:
4761 loinc_str = '%s: %s%s' % (
4762 loinc,
4763 loinc_detail['term'],
4764 gmTools.coalesce(loinc_detail['example_units'], '', ' (%s)')
4765 )
4766 self._LBL_loinc.SetLabel(loinc_str)
4767
4768
4790
4791
4795
4796
4798 loincs2remove = self._LCTRL_loincs.selected_item_data
4799 if loincs2remove is None:
4800 return
4801 for loinc in loincs2remove:
4802 try:
4803 while True:
4804 self.__loincs.remove(loinc[0])
4805 except ValueError:
4806 pass
4807 self.__refresh_loinc_list()
4808
4809
4810
4811
4812 if __name__ == '__main__':
4813
4814 from Gnumed.pycommon import gmLog2
4815 from Gnumed.wxpython import gmPatSearchWidgets
4816
4817 gmI18N.activate_locale()
4818 gmI18N.install_domain()
4819 gmDateTime.init()
4820
4821
4829
4837
4838
4839
4840
4841
4842
4843
4844 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
4845
4846 test_test_ea_pnl()
4847
4848
4849
4850