1 """GNUmed measurement widgets."""
2
3 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
4 __license__ = "GPL"
5
6
7 import sys
8 import logging
9 import datetime as pyDT
10 import decimal
11 import os
12 import subprocess
13 import io
14 import os.path
15
16
17 import wx
18 import wx.grid
19 import wx.adv as wxh
20
21
22 if __name__ == '__main__':
23 sys.path.insert(0, '../../')
24 from Gnumed.pycommon import gmTools
25 from Gnumed.pycommon import gmNetworkTools
26 from Gnumed.pycommon import gmI18N
27 from Gnumed.pycommon import gmShellAPI
28 from Gnumed.pycommon import gmCfg
29 from Gnumed.pycommon import gmDateTime
30 from Gnumed.pycommon import gmMatchProvider
31 from Gnumed.pycommon import gmDispatcher
32 from Gnumed.pycommon import gmMimeLib
33
34 from Gnumed.business import gmPerson
35 from Gnumed.business import gmStaff
36 from Gnumed.business import gmPathLab
37 from Gnumed.business import gmPraxis
38 from Gnumed.business import gmLOINC
39 from Gnumed.business import gmForms
40 from Gnumed.business import gmPersonSearch
41 from Gnumed.business import gmOrganization
42 from Gnumed.business import gmHL7
43 from Gnumed.business import gmIncomingData
44 from Gnumed.business import gmDocuments
45
46 from Gnumed.wxpython import gmRegetMixin
47 from Gnumed.wxpython import gmPlugin
48 from Gnumed.wxpython import gmEditArea
49 from Gnumed.wxpython import gmPhraseWheel
50 from Gnumed.wxpython import gmListWidgets
51 from Gnumed.wxpython import gmGuiHelpers
52 from Gnumed.wxpython import gmAuthWidgets
53 from Gnumed.wxpython import gmOrganizationWidgets
54 from Gnumed.wxpython import gmEMRStructWidgets
55 from Gnumed.wxpython import gmCfgWidgets
56 from Gnumed.wxpython import gmDocumentWidgets
57
58
59 _log = logging.getLogger('gm.ui')
60
61
62
63
65
66 if parent is None:
67 parent = wx.GetApp().GetTopWindow()
68
69
70 paths = gmTools.gmPaths()
71 dlg = wx.FileDialog (
72 parent = parent,
73 message = _('Show HL7 file:'),
74
75 defaultDir = os.path.join(paths.home_dir, 'gnumed'),
76 wildcard = "hl7 files|*.hl7|HL7 files|*.HL7|all files|*",
77 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
78 )
79 choice = dlg.ShowModal()
80 hl7_name = dlg.GetPath()
81 dlg.DestroyLater()
82 if choice != wx.ID_OK:
83 return False
84
85 formatted_name = gmHL7.format_hl7_file (
86 hl7_name,
87 skip_empty_fields = True,
88 return_filename = True,
89 fix_hl7 = True
90 )
91 gmMimeLib.call_viewer_on_file(aFile = formatted_name, block = False)
92 return True
93
94
96
97 if parent is None:
98 parent = wx.GetApp().GetTopWindow()
99
100
101 paths = gmTools.gmPaths()
102 dlg = wx.FileDialog (
103 parent = parent,
104 message = _('Extract HL7 from XML file:'),
105
106 defaultDir = os.path.join(paths.home_dir, 'gnumed'),
107 wildcard = "xml files|*.xml|XML files|*.XML|all files|*",
108 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
109 )
110 choice = dlg.ShowModal()
111 xml_name = dlg.GetPath()
112 dlg.DestroyLater()
113 if choice != wx.ID_OK:
114 return False
115
116 target_dir = os.path.split(xml_name)[0]
117 xml_path = './/Message'
118 hl7_name = gmHL7.extract_HL7_from_XML_CDATA(xml_name, xml_path, target_dir = target_dir)
119 if hl7_name is None:
120 gmGuiHelpers.gm_show_error (
121 title = _('Extracting HL7 from XML file'),
122 error = (
123 'Cannot unwrap HL7 data from XML file\n'
124 '\n'
125 ' [%s]\n'
126 '\n'
127 '(CDATA of [%s] nodes)'
128 ) % (
129 xml_name,
130 xml_path
131 )
132 )
133 return False
134
135 gmDispatcher.send(signal = 'statustext', msg = _('Unwrapped HL7 into [%s] from [%s].') % (hl7_name, xml_name), beep = False)
136 return True
137
138
140
141 if parent is None:
142 parent = wx.GetApp().GetTopWindow()
143
144 paths = gmTools.gmPaths()
145 dlg = wx.FileDialog (
146 parent = parent,
147 message = _('Select HL7 file for staging:'),
148
149 defaultDir = os.path.join(paths.home_dir, 'gnumed'),
150 wildcard = ".hl7 files|*.hl7|.HL7 files|*.HL7|all files|*",
151 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
152 )
153 choice = dlg.ShowModal()
154 hl7_name = dlg.GetPath()
155 dlg.DestroyLater()
156 if choice != wx.ID_OK:
157 return False
158
159 target_dir = os.path.join(paths.home_dir, '.gnumed', 'hl7')
160 success, PID_names = gmHL7.split_hl7_file(hl7_name, target_dir = target_dir, encoding = 'utf8')
161 if not success:
162 gmGuiHelpers.gm_show_error (
163 title = _('Staging HL7 file'),
164 error = _(
165 'There was a problem with splitting the HL7 file\n'
166 '\n'
167 ' %s'
168 ) % hl7_name
169 )
170 return False
171
172 failed_files = []
173 for PID_name in PID_names:
174 if not gmHL7.stage_single_PID_hl7_file(PID_name, source = _('generic'), encoding = 'utf8'):
175 failed_files.append(PID_name)
176 if len(failed_files) > 0:
177 gmGuiHelpers.gm_show_error (
178 title = _('Staging HL7 file'),
179 error = _(
180 'There was a problem with staging the following files\n'
181 '\n'
182 ' %s'
183 ) % '\n '.join(failed_files)
184 )
185 return False
186
187 gmDispatcher.send(signal = 'statustext', msg = _('Staged HL7 from [%s].') % hl7_name, beep = False)
188 return True
189
190
212
213 def import_hl7(staged_item):
214 if staged_item is None:
215 return False
216 if 'HL7' not in staged_item['data_type']:
217 return False
218 unset_identity_on_error = False
219 if staged_item['pk_identity_disambiguated'] is None:
220 pat = gmPerson.gmCurrentPatient()
221 if pat.connected:
222 answer = gmGuiHelpers.gm_show_question (
223 title = _('Importing HL7 data'),
224 question = _(
225 'There has not been a patient explicitely associated\n'
226 'with this chunk of HL7 data. However, the data file\n'
227 'contains the following patient identification information:\n'
228 '\n'
229 ' %s\n'
230 '\n'
231 'Do you want to import the HL7 under the current patient ?\n'
232 '\n'
233 ' %s\n'
234 '\n'
235 'Selecting [NO] makes GNUmed try to find a patient matching the HL7 data.\n'
236 ) % (
237 staged_item.patient_identification,
238 pat['description_gender']
239 ),
240 cancel_button = True
241 )
242 if answer is None:
243 return False
244 if answer is True:
245 unset_identity_on_error = True
246 staged_item['pk_identity_disambiguated'] = pat.ID
247
248 success, log_name = gmHL7.process_staged_single_PID_hl7_file(staged_item)
249 if success:
250 return True
251
252 if unset_identity_on_error:
253 staged_item['pk_identity_disambiguated'] = None
254 staged_item.save()
255
256 gmGuiHelpers.gm_show_error (
257 error = _('Error processing HL7 data.'),
258 title = _('Processing staged HL7 data.')
259 )
260 return False
261
262
263 def delete(staged_item):
264 if staged_item is None:
265 return False
266 do_delete = gmGuiHelpers.gm_show_question (
267 title = _('Deleting incoming data'),
268 question = _(
269 'Do you really want to delete the incoming data ?\n'
270 '\n'
271 'Note that deletion is not reversible.'
272 )
273 )
274 if not do_delete:
275 return False
276 return gmIncomingData.delete_incoming_data(pk_incoming_data = staged_item['pk_incoming_data_unmatched'])
277
278 def refresh(lctrl):
279 incoming = gmIncomingData.get_incoming_data()
280 items = [ [
281 gmTools.coalesce(i['data_type'], ''),
282 '%s, %s (%s) %s' % (
283 gmTools.coalesce(i['lastnames'], ''),
284 gmTools.coalesce(i['firstnames'], ''),
285 gmDateTime.pydt_strftime(dt = i['dob'], format = '%Y %b %d', accuracy = gmDateTime.acc_days, none_str = _('unknown DOB')),
286 gmTools.coalesce(i['gender'], '')
287 ),
288 gmTools.coalesce(i['external_data_id'], ''),
289 i['pk_incoming_data_unmatched']
290 ] for i in incoming ]
291 lctrl.set_string_items(items)
292 lctrl.set_data(incoming)
293
294 gmListWidgets.get_choices_from_list (
295 parent = parent,
296 msg = None,
297 caption = _('Showing unmatched incoming data'),
298 columns = [ _('Type'), _('Identification'), _('Reference'), '#' ],
299 single_selection = True,
300 can_return_empty = False,
301 ignore_OK_button = True,
302 refresh_callback = refresh,
303
304
305 delete_callback = delete,
306 left_extra_button = [_('Show'), _('Show formatted HL7'), show_hl7],
307 middle_extra_button = [_('Import'), _('Import HL7 data into patient chart'), import_hl7]
308
309 )
310
311
312
313
343
344
345 -def edit_measurement(parent=None, measurement=None, single_entry=False, presets=None):
358
359
371
372 def delete(measurement):
373 gmPathLab.delete_test_result(result = measurement)
374 return True
375
376 def do_review(lctrl):
377 data = lctrl.get_selected_item_data()
378 if len(data) == 0:
379 return
380 return review_tests(parent = parent, tests = data)
381
382 def do_plot(lctrl):
383 data = lctrl.get_selected_item_data()
384 if len(data) == 0:
385 return
386 return plot_measurements(parent = parent, tests = data)
387
388 def get_tooltip(measurement):
389 return measurement.format(with_review=True, with_evaluation=True, with_ranges=True)
390
391 def refresh(lctrl):
392 results = emr.get_test_results(order_by = 'clin_when DESC, unified_abbrev, unified_name')
393 items = [ [
394 gmDateTime.pydt_strftime (
395 r['clin_when'],
396 '%Y %b %d %H:%M',
397 accuracy = gmDateTime.acc_minutes
398 ),
399 r['unified_abbrev'],
400 '%s%s%s%s' % (
401 gmTools.bool2subst (
402 boolean = (not r['reviewed'] or (not r['review_by_you'] and r['you_are_responsible'])),
403 true_return = 'u' + gmTools.u_writing_hand,
404 false_return = ''
405 ),
406 r['unified_val'],
407 gmTools.coalesce(r['val_unit'], '', ' %s'),
408 gmTools.coalesce(r['abnormality_indicator'], '', ' %s')
409 ),
410 r['unified_name'],
411
412
413
414
415
416
417
418
419
420
421
422
423 gmTools.coalesce(r['comment'], ''),
424 r['pk_test_result']
425 ] for r in results ]
426 lctrl.set_string_items(items)
427 lctrl.set_data(results)
428
429
430 msg = _('Test results (ordered reverse-chronologically)')
431
432 return gmListWidgets.get_choices_from_list (
433 parent = parent,
434 msg = msg,
435 caption = _('Showing test results.'),
436 columns = [ _('When'), _('Abbrev'), _('Value'), _('Name'), _('Comment'), '#' ],
437 single_selection = single_selection,
438 can_return_empty = False,
439 refresh_callback = refresh,
440 edit_callback = edit,
441 new_callback = edit,
442 delete_callback = delete,
443 list_tooltip_callback = get_tooltip,
444 left_extra_button = (_('Review'), _('Review current selection'), do_review, True),
445 middle_extra_button = (_('Plot'), _('Plot current selection'), do_plot, True)
446 )
447
448
466
467
498
499
501
502 option = 'form_templates.default_gnuplot_template'
503
504 dbcfg = gmCfg.cCfgSQL()
505
506
507 default_template_name = dbcfg.get2 (
508 option = option,
509 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
510 bias = 'user'
511 )
512
513
514 if default_template_name is None:
515 gmDispatcher.send('statustext', msg = _('No default Gnuplot template configured.'), beep = False)
516 default_template = configure_default_gnuplot_template(parent = parent)
517
518 if default_template is None:
519 gmGuiHelpers.gm_show_error (
520 aMessage = _('There is no default Gnuplot one-type script template configured.'),
521 aTitle = _('Plotting test results')
522 )
523 return None
524 return default_template
525
526
527
528 try:
529 name, ver = default_template_name.split(' - ')
530 except:
531
532 _log.exception('problem splitting Gnuplot script template name [%s]', default_template_name)
533 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading Gnuplot script template.'), beep = True)
534 return None
535
536 default_template = gmForms.get_form_template(name_long = name, external_version = ver)
537 if default_template is None:
538 default_template = configure_default_gnuplot_template(parent = parent)
539
540 if default_template is None:
541 gmGuiHelpers.gm_show_error (
542 aMessage = _('Cannot load default Gnuplot script template [%s - %s]') % (name, ver),
543 aTitle = _('Plotting test results')
544 )
545 return None
546
547 return default_template
548
549
550 -def plot_measurements(parent=None, tests=None, format=None, show_year = True, use_default_template=False):
588
589
590 -def plot_adjacent_measurements(parent=None, test=None, format=None, show_year=True, plot_singular_result=True, use_default_template=False):
591
592 earlier, later = test.get_adjacent_results(desired_earlier_results = 2, desired_later_results = 2)
593 results2plot = []
594 if earlier is not None:
595 results2plot.extend(earlier)
596 results2plot.append(test)
597 if later is not None:
598 results2plot.extend(later)
599 if len(results2plot) == 1:
600 if not plot_singular_result:
601 return
602 plot_measurements (
603 parent = parent,
604 tests = results2plot,
605 format = format,
606 show_year = show_year,
607 use_default_template = use_default_template
608 )
609
610
611
612
613
614
615
616
617
618
619
620
621 from Gnumed.wxGladeWidgets import wxgLabRelatedDocumentsPnl
622
766
767
768 from Gnumed.wxGladeWidgets import wxgMeasurementsAsListPnl
769
770 -class cMeasurementsAsListPnl(wxgMeasurementsAsListPnl.wxgMeasurementsAsListPnl, gmRegetMixin.cRegetOnPaintMixin):
771 """A class for displaying all measurement results as a simple list.
772
773 - operates on a cPatient instance handed to it and NOT on the currently active patient
774 """
784
785
786
787
789 self._LCTRL_results.set_columns([_('When'), _('Test'), _('Result'), _('Reference')])
790 self._LCTRL_results.edit_callback = self._on_edit
791 self._PNL_related_documents.lab_reference = None
792
793
796
797
799 if self.__patient is None:
800 self._LCTRL_results.set_string_items([])
801 self._TCTRL_measurements.SetValue('')
802 self._PNL_related_documents.lab_reference = None
803 return
804
805 results = self.__patient.emr.get_test_results(order_by = 'clin_when DESC, unified_abbrev, unified_name')
806 items = []
807 data = []
808 for r in results:
809 range_info = gmTools.coalesce (
810 r.formatted_clinical_range,
811 r.formatted_normal_range
812 )
813 review = gmTools.bool2subst (
814 r['reviewed'],
815 '',
816 ' ' + gmTools.u_writing_hand,
817 ' ' + gmTools.u_writing_hand
818 )
819 items.append ([
820 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes),
821 r['abbrev_tt'],
822 '%s%s%s%s' % (
823 gmTools.strip_empty_lines(text = r['unified_val'])[0],
824 gmTools.coalesce(r['val_unit'], '', ' %s'),
825 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
826 review
827 ),
828 gmTools.coalesce(range_info, '')
829 ])
830 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
831
832 self._LCTRL_results.set_string_items(items)
833 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
834 self._LCTRL_results.set_data(data)
835 if len(items) > 0:
836 self._LCTRL_results.Select(idx = 0, on = 1)
837 self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted'])
838
839 self._LCTRL_results.SetFocus()
840
841
848
849
850
851
853 if self.__patient is None:
854 return True
855
856 if kwds['pk_identity'] is not None:
857 if kwds['pk_identity'] != self.__patient.ID:
858 return True
859
860 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
861 return True
862
863 self._schedule_data_reget()
864 return True
865
866
872
873
874
875
877 self.__repopulate_ui()
878 return True
879
880
881
882
884 return self.__patient
885
887 if (self.__patient is None) and (patient is None):
888 return
889 if (self.__patient is None) or (patient is None):
890 self.__patient = patient
891 self._schedule_data_reget()
892 return
893 if self.__patient.ID == patient.ID:
894 return
895 self.__patient = patient
896 self._schedule_data_reget()
897
898 patient = property(_get_patient, _set_patient)
899
900
901 from Gnumed.wxGladeWidgets import wxgMeasurementsByDayPnl
902
903 -class cMeasurementsByDayPnl(wxgMeasurementsByDayPnl.wxgMeasurementsByDayPnl, gmRegetMixin.cRegetOnPaintMixin):
904 """A class for displaying measurement results as a list partitioned by day.
905
906 - operates on a cPatient instance handed to it and NOT on the currently active patient
907 """
918
919
920
921
923 self._LCTRL_days.set_columns([_('Day')])
924 self._LCTRL_results.set_columns([_('Time'), _('Test'), _('Result'), _('Reference')])
925 self._LCTRL_results.edit_callback = self._on_edit
926 self._PNL_related_documents.lab_reference = None
927
928
931
932
938
939
958
959
966
967
968
969
971 if self.__patient is None:
972 return True
973
974 if kwds['pk_identity'] is not None:
975 if kwds['pk_identity'] != self.__patient.ID:
976 return True
977
978 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
979 return True
980
981 self._schedule_data_reget()
982 return True
983
984
986 event.Skip()
987
988 day = self._LCTRL_days.get_item_data(item_idx = event.Index)['clin_when_day']
989 results = self.__patient.emr.get_results_for_day(timestamp = day)
990 items = []
991 data = []
992 for r in results:
993 range_info = gmTools.coalesce (
994 r.formatted_clinical_range,
995 r.formatted_normal_range
996 )
997 review = gmTools.bool2subst (
998 r['reviewed'],
999 '',
1000 ' ' + gmTools.u_writing_hand,
1001 ' ' + gmTools.u_writing_hand
1002 )
1003 items.append ([
1004 gmDateTime.pydt_strftime(r['clin_when'], '%H:%M'),
1005 r['abbrev_tt'],
1006 '%s%s%s%s' % (
1007 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1008 gmTools.coalesce(r['val_unit'], '', ' %s'),
1009 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1010 review
1011 ),
1012 gmTools.coalesce(range_info, '')
1013 ])
1014 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
1015
1016 self._LCTRL_results.set_string_items(items)
1017 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1018 self._LCTRL_results.set_data(data)
1019 self._LCTRL_results.Select(idx = 0, on = 1)
1020
1021
1027
1028
1029
1030
1032 self.__repopulate_ui()
1033 return True
1034
1035
1036
1037
1039 return self.__patient
1040
1042 if (self.__patient is None) and (patient is None):
1043 return
1044 if patient is None:
1045 self.__patient = None
1046 self.__clear()
1047 return
1048 if self.__patient is None:
1049 self.__patient = patient
1050 self._schedule_data_reget()
1051 return
1052 if self.__patient.ID == patient.ID:
1053 return
1054 self.__patient = patient
1055 self._schedule_data_reget()
1056
1057 patient = property(_get_patient, _set_patient)
1058
1059
1060 from Gnumed.wxGladeWidgets import wxgMeasurementsByIssuePnl
1061
1062 -class cMeasurementsByIssuePnl(wxgMeasurementsByIssuePnl.wxgMeasurementsByIssuePnl, gmRegetMixin.cRegetOnPaintMixin):
1063 """A class for displaying measurement results as a list partitioned by issue/episode.
1064
1065 - operates on a cPatient instance handed to it and NOT on the currently active patient
1066 """
1076
1077
1078
1079
1081 self._LCTRL_issues.set_columns([_('Problem')])
1082 self._LCTRL_results.set_columns([_('When'), _('Test'), _('Result'), _('Reference')])
1083 self._PNL_related_documents.lab_reference = None
1084
1085
1091
1092
1098
1099
1115
1116
1123
1124
1125
1126
1128 if self.__patient is None:
1129 return True
1130
1131 if kwds['pk_identity'] is not None:
1132 if kwds['pk_identity'] != self.__patient.ID:
1133 return True
1134
1135 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1136 return True
1137
1138 self._schedule_data_reget()
1139 return True
1140
1141
1143 event.Skip()
1144
1145 pk_issue = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_issue']
1146 if pk_issue is None:
1147 pk_episode = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_episode']
1148 results = self.__patient.emr.get_results_for_episode(pk_episode = pk_episode)
1149 else:
1150 results = self.__patient.emr.get_results_for_issue(pk_health_issue = pk_issue)
1151 items = []
1152 data = []
1153 for r in results:
1154 range_info = gmTools.coalesce (
1155 r.formatted_clinical_range,
1156 r.formatted_normal_range
1157 )
1158 review = gmTools.bool2subst (
1159 r['reviewed'],
1160 '',
1161 ' ' + gmTools.u_writing_hand,
1162 ' ' + gmTools.u_writing_hand
1163 )
1164 items.append ([
1165 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M'),
1166 r['abbrev_tt'],
1167 '%s%s%s%s' % (
1168 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1169 gmTools.coalesce(r['val_unit'], '', ' %s'),
1170 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1171 review
1172 ),
1173 gmTools.coalesce(range_info, '')
1174 ])
1175 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
1176
1177 self._LCTRL_results.set_string_items(items)
1178 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1179 self._LCTRL_results.set_data(data)
1180 self._LCTRL_results.Select(idx = 0, on = 1)
1181 self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted'])
1182
1183
1189
1190
1191
1192
1194 self.__repopulate_ui()
1195 return True
1196
1197
1198
1199
1201 return self.__patient
1202
1204 if (self.__patient is None) and (patient is None):
1205 return
1206 if patient is None:
1207 self.__patient = None
1208 self.__clear()
1209 return
1210 if self.__patient is None:
1211 self.__patient = patient
1212 self._schedule_data_reget()
1213 return
1214 if self.__patient.ID == patient.ID:
1215 return
1216 self.__patient = patient
1217 self._schedule_data_reget()
1218
1219 patient = property(_get_patient, _set_patient)
1220
1221
1222 from Gnumed.wxGladeWidgets import wxgMeasurementsByBatteryPnl
1223
1225 """A grid class for displaying measurement results filtered by battery/panel.
1226
1227 - operates on a cPatient instance handed to it and NOT on the currently active patient
1228 """
1238
1239
1240
1241
1244
1245
1251
1252
1254 self._GRID_results_battery.patient = self.__patient
1255 return True
1256
1257
1259 if panel is None:
1260 self._TCTRL_panel_comment.SetValue('')
1261 self._GRID_results_battery.panel_to_show = None
1262 else:
1263 pnl = self._PRW_panel.GetData(as_instance = True)
1264 self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
1265 pnl['comment'],
1266 ''
1267 ))
1268 self._GRID_results_battery.panel_to_show = pnl
1269
1270
1271
1273 self._TCTRL_panel_comment.SetValue('')
1274 if self._PRW_panel.GetValue().strip() == '':
1275 self._GRID_results_battery.panel_to_show = None
1276
1277
1278
1279
1280
1282 if self.__patient is None:
1283 return True
1284
1285 if kwds['pk_identity'] is not None:
1286 if kwds['pk_identity'] != self.__patient.ID:
1287 return True
1288
1289 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1290 return True
1291
1292 self._schedule_data_reget()
1293 return True
1294
1295
1298
1299
1301 wx.CallAfter(self.__on_panel_selected, panel=panel)
1302
1303
1305 wx.CallAfter(self.__on_panel_selection_modified)
1306
1307
1308
1309
1311 self.__repopulate_ui()
1312 return True
1313
1314
1315
1316
1318 return self.__patient
1319
1321 if (self.__patient is None) and (patient is None):
1322 return
1323 if (self.__patient is None) or (patient is None):
1324 self.__patient = patient
1325 self._schedule_data_reget()
1326 return
1327 if self.__patient.ID == patient.ID:
1328 return
1329 self.__patient = patient
1330 self._schedule_data_reget()
1331
1332 patient = property(_get_patient, _set_patient)
1333
1334
1335 from Gnumed.wxGladeWidgets import wxgMeasurementsAsMostRecentListPnl
1336
1338 """A list ctrl class for displaying measurement results.
1339
1340 - most recent results
1341 - possibly filtered by battery/panel
1342
1343 - operates on a cPatient instance handed to it and NOT on the currently active patient
1344 """
1354
1355
1356
1357
1359 self._LCTRL_results.set_columns([_('Test'), _('Result'), _('When'), _('Range')])
1360 self._CHBOX_show_missing.Disable()
1361 self._PNL_related_documents.lab_reference = None
1362
1363
1372
1373
1375
1376 self._TCTRL_details.SetValue('')
1377 self._PNL_related_documents.lab_reference = None
1378 if self.__patient is None:
1379 self._LCTRL_results.remove_items_safely()
1380 return
1381
1382 pnl = self._PRW_panel.GetData(as_instance = True)
1383 if pnl is None:
1384 results = gmPathLab.get_most_recent_result_for_test_types (
1385 pk_patient = self.__patient.ID,
1386 consider_meta_type = True
1387 )
1388 else:
1389 results = pnl.get_most_recent_results (
1390 pk_patient = self.__patient.ID,
1391
1392 group_by_meta_type = True,
1393 include_missing = self._CHBOX_show_missing.IsChecked()
1394 )
1395 items = []
1396 data = []
1397 for r in results:
1398 if isinstance(r, gmPathLab.cTestResult):
1399 result_type = gmTools.coalesce (
1400 value2test = r['pk_meta_test_type'],
1401 return_instead = r['abbrev_tt'],
1402 value2return = '%s%s' % (gmTools.u_sum, r['abbrev_meta'])
1403 )
1404 review = gmTools.bool2subst (
1405 r['reviewed'],
1406 '',
1407 ' ' + gmTools.u_writing_hand,
1408 ' ' + gmTools.u_writing_hand
1409 )
1410 result_val = '%s%s%s%s' % (
1411 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1412 gmTools.coalesce(r['val_unit'], '', ' %s'),
1413 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1414 review
1415 )
1416 result_when = _('%s ago (%s)') % (
1417 gmDateTime.format_interval_medically(interval = gmDateTime.pydt_now_here() - r['clin_when']),
1418 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes)
1419 )
1420 range_info = gmTools.coalesce (
1421 r.formatted_clinical_range,
1422 r.formatted_normal_range
1423 )
1424 tt = r.format(with_source_data = True)
1425 else:
1426 result_type = r
1427 result_val = _('missing')
1428 loinc_data = gmLOINC.loinc2data(r)
1429 if loinc_data is None:
1430 result_when = _('LOINC not found')
1431 tt = u''
1432 else:
1433 result_when = loinc_data['term']
1434 tt = gmLOINC.format_loinc(r)
1435 range_info = None
1436 items.append([result_type, result_val, result_when, gmTools.coalesce(range_info, '')])
1437 data.append({'data': r, 'formatted': tt})
1438
1439 self._LCTRL_results.set_string_items(items)
1440 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1441 self._LCTRL_results.set_data(data)
1442
1443 if len(items) > 0:
1444 self._LCTRL_results.Select(idx = 0, on = 1)
1445 self._LCTRL_results.SetFocus()
1446
1447 return True
1448
1449
1451 if panel is None:
1452 self._TCTRL_panel_comment.SetValue('')
1453 self._CHBOX_show_missing.Disable()
1454 else:
1455 pnl = self._PRW_panel.GetData(as_instance = True)
1456 self._TCTRL_panel_comment.SetValue(gmTools.coalesce(pnl['comment'], ''))
1457 self.__repopulate_ui()
1458 self._CHBOX_show_missing.Enable()
1459
1460
1462 self._TCTRL_panel_comment.SetValue('')
1463 if self._PRW_panel.Value.strip() == u'':
1464 self.__repopulate_ui()
1465 self._CHBOX_show_missing.Disable()
1466
1467
1468
1469
1471 if self.__patient is None:
1472 return True
1473
1474 if kwds['pk_identity'] is not None:
1475 if kwds['pk_identity'] != self.__patient.ID:
1476 return True
1477
1478 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results', 'clin.test_panel']:
1479 return True
1480
1481 self._schedule_data_reget()
1482 return True
1483
1484
1487
1488
1490 wx.CallAfter(self.__on_panel_selected, panel = panel)
1491
1492
1494 wx.CallAfter(self.__on_panel_selection_modified)
1495
1496
1505
1506
1514
1515
1517 event.Skip()
1518
1519 if self._PRW_panel.GetData(as_instance = False) is None:
1520 return
1521 self.__repopulate_ui()
1522
1523
1524
1525
1527 self.__repopulate_ui()
1528 return True
1529
1530
1531
1532
1534 return self.__patient
1535
1537 if (self.__patient is None) and (patient is None):
1538 return
1539 if (self.__patient is None) or (patient is None):
1540 self.__patient = patient
1541 self._schedule_data_reget()
1542 return
1543 if self.__patient.ID == patient.ID:
1544 return
1545 self.__patient = patient
1546 self._schedule_data_reget()
1547
1548 patient = property(_get_patient, _set_patient)
1549
1550
1551 from Gnumed.wxGladeWidgets import wxgMeasurementsAsTablePnl
1552
1553 -class cMeasurementsAsTablePnl(wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl, gmRegetMixin.cRegetOnPaintMixin):
1554 """A panel for holding a grid displaying all measurement results.
1555
1556 - operates on a cPatient instance handed to it and NOT on the currently active patient
1557 """
1567
1568
1569
1570
1572 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
1573
1574 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
1575 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
1576
1577 item = self.__action_button_popup.Append(-1, _('Plot'))
1578 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588 item = self.__action_button_popup.Append(-1, _('&Delete'))
1589 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
1590
1591
1592
1593
1594 self._GRID_results_all.show_by_panel = False
1595
1596
1599
1600
1602 self._GRID_results_all.patient = self.__patient
1603
1604 self.Layout()
1605 return True
1606
1607
1610
1611
1614
1615
1618
1619
1620
1621
1623 if self.__patient is None:
1624 return True
1625
1626 if kwds['pk_identity'] is not None:
1627 if kwds['pk_identity'] != self.__patient.ID:
1628 return True
1629
1630 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1631 return True
1632
1633 self._schedule_data_reget()
1634 return True
1635
1636
1639
1640
1644
1645
1648
1649
1655
1656
1657
1658
1660 self.__repopulate_ui()
1661 return True
1662
1663
1664
1665
1667 return self.__patient
1668
1670 if (self.__patient is None) and (patient is None):
1671 return
1672 if (self.__patient is None) or (patient is None):
1673 self.__patient = patient
1674 self._schedule_data_reget()
1675 return
1676 if self.__patient.ID == patient.ID:
1677 return
1678 self.__patient = patient
1679 self._schedule_data_reget()
1680
1681 patient = property(_get_patient, _set_patient)
1682
1683
1684
1685
1687 """Notebook displaying measurements pages:
1688
1689 - by test battery
1690 - by day
1691 - by issue/episode
1692 - most-recent list, perhaps by panel
1693 - full grid
1694 - full list
1695
1696 Used as a main notebook plugin page.
1697
1698 Operates on the active patient.
1699 """
1700
1715
1716
1717
1718
1720 for page_idx in range(self.GetPageCount()):
1721 page = self.GetPage(page_idx)
1722 page.patient = None
1723
1724
1725 - def _post_patient_selection(self, **kwds):
1726 for page_idx in range(self.GetPageCount()):
1727 page = self.GetPage(page_idx)
1728 page.patient = self.__patient.patient
1729
1730
1731
1732
1734 if self.__patient.connected:
1735 pat = self.__patient.patient
1736 else:
1737 pat = None
1738 for page_idx in range(self.GetPageCount()):
1739 page = self.GetPage(page_idx)
1740 page.patient = pat
1741
1742 return True
1743
1744
1745
1746
1748
1749
1750 new_page = cMeasurementsByDayPnl(self, -1)
1751 new_page.patient = None
1752 self.AddPage (
1753 page = new_page,
1754 text = _('Days'),
1755 select = True
1756 )
1757
1758
1759 new_page = cMeasurementsByIssuePnl(self, -1)
1760 new_page.patient = None
1761 self.AddPage (
1762 page = new_page,
1763 text = _('Problems'),
1764 select = False
1765 )
1766
1767
1768 new_page = cMeasurementsByBatteryPnl(self, -1)
1769 new_page.patient = None
1770 self.AddPage (
1771 page = new_page,
1772 text = _('Panels'),
1773 select = False
1774 )
1775
1776
1777 new_page = cMeasurementsAsMostRecentListPnl(self, -1)
1778 new_page.patient = None
1779 self.AddPage (
1780 page = new_page,
1781 text = _('Most recent'),
1782 select = False
1783 )
1784
1785
1786 new_page = cMeasurementsAsTablePnl(self, -1)
1787 new_page.patient = None
1788 self.AddPage (
1789 page = new_page,
1790 text = _('Table'),
1791 select = False
1792 )
1793
1794
1795 new_page = cMeasurementsAsListPnl(self, -1)
1796 new_page.patient = None
1797 self.AddPage (
1798 page = new_page,
1799 text = _('List'),
1800 select = False
1801 )
1802
1803
1804
1805
1807 return self.__patient
1808
1810 self.__patient = patient
1811 if self.__patient.connected:
1812 pat = self.__patient.patient
1813 else:
1814 pat = None
1815 for page_idx in range(self.GetPageCount()):
1816 page = self.GetPage(page_idx)
1817 page.patient = pat
1818
1819 patient = property(_get_patient, _set_patient)
1820
1821
1823 """A grid class for displaying measurement results.
1824
1825 - operates on a cPatient instance handed to it
1826 - does NOT listen to the currently active patient
1827 - thereby it can display any patient at any time
1828 """
1829
1830
1831
1832
1833
1835
1836 wx.grid.Grid.__init__(self, *args, **kwargs)
1837
1838 self.__patient = None
1839 self.__panel_to_show = None
1840 self.__show_by_panel = False
1841 self.__cell_data = {}
1842 self.__row_label_data = []
1843 self.__col_label_data = []
1844
1845 self.__prev_row = None
1846 self.__prev_col = None
1847 self.__prev_label_row = None
1848 self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::'))
1849
1850 self.__init_ui()
1851 self.__register_events()
1852
1853
1854
1855
1857 if not self.IsSelection():
1858 gmDispatcher.send(signal = 'statustext', msg = _('No results selected for deletion.'))
1859 return True
1860
1861 selected_cells = self.get_selected_cells()
1862 if len(selected_cells) > 20:
1863 results = None
1864 msg = _(
1865 'There are %s results marked for deletion.\n'
1866 '\n'
1867 'Are you sure you want to delete these results ?'
1868 ) % len(selected_cells)
1869 else:
1870 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1871 txt = '\n'.join([ '%s %s (%s): %s %s%s' % (
1872 r['clin_when'].strftime('%x %H:%M'),
1873 r['unified_abbrev'],
1874 r['unified_name'],
1875 r['unified_val'],
1876 r['val_unit'],
1877 gmTools.coalesce(r['abnormality_indicator'], '', ' (%s)')
1878 ) for r in results
1879 ])
1880 msg = _(
1881 'The following results are marked for deletion:\n'
1882 '\n'
1883 '%s\n'
1884 '\n'
1885 'Are you sure you want to delete these results ?'
1886 ) % txt
1887
1888 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
1889 self,
1890 -1,
1891 caption = _('Deleting test results'),
1892 question = msg,
1893 button_defs = [
1894 {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False},
1895 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True}
1896 ]
1897 )
1898 decision = dlg.ShowModal()
1899
1900 if decision == wx.ID_YES:
1901 if results is None:
1902 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1903 for result in results:
1904 gmPathLab.delete_test_result(result)
1905
1906
1908 if not self.IsSelection():
1909 gmDispatcher.send(signal = 'statustext', msg = _('Cannot sign results. No results selected.'))
1910 return True
1911
1912 selected_cells = self.get_selected_cells()
1913 tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1914
1915 return review_tests(parent = self, tests = tests)
1916
1917
1919
1920 if not self.IsSelection():
1921 gmDispatcher.send(signal = 'statustext', msg = _('Cannot plot results. No results selected.'))
1922 return True
1923
1924 tests = self.__cells_to_data (
1925 cells = self.get_selected_cells(),
1926 exclude_multi_cells = False,
1927 auto_include_multi_cells = True
1928 )
1929
1930 plot_measurements(parent = self, tests = tests)
1931
1932
1934 """Assemble list of all selected cells."""
1935
1936 all_selected_cells = []
1937
1938 all_selected_cells += [ cell_coords.Get() for cell_coords in self.GetSelectedCells() ]
1939
1940 fully_selected_rows = self.GetSelectedRows()
1941 all_selected_cells += list (
1942 (row, col)
1943 for row in fully_selected_rows
1944 for col in range(self.GetNumberCols())
1945 )
1946
1947 fully_selected_cols = self.GetSelectedCols()
1948 all_selected_cells += list (
1949 (row, col)
1950 for row in range(self.GetNumberRows())
1951 for col in fully_selected_cols
1952 )
1953
1954 selected_blocks = zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight())
1955 for top_left_corner, bottom_right_corner in selected_blocks:
1956 all_selected_cells += [
1957 (row, col)
1958 for row in range(top_left_corner[0], bottom_right_corner[0] + 1)
1959 for col in range(top_left_corner[1], bottom_right_corner[1] + 1)
1960 ]
1961 return set(all_selected_cells)
1962
1963
1964 - def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
1965 """Select a range of cells according to criteria.
1966
1967 unsigned_only: include only those which are not signed at all yet
1968 accountable_only: include only those for which the current user is responsible
1969 keep_preselections: broaden (rather than replace) the range of selected cells
1970
1971 Combinations are powerful !
1972 """
1973 wx.BeginBusyCursor()
1974 self.BeginBatch()
1975
1976 if not keep_preselections:
1977 self.ClearSelection()
1978
1979 for col_idx in self.__cell_data.keys():
1980 for row_idx in self.__cell_data[col_idx].keys():
1981
1982
1983 do_not_include = False
1984 for result in self.__cell_data[col_idx][row_idx]:
1985 if unsigned_only:
1986 if result['reviewed']:
1987 do_not_include = True
1988 break
1989 if accountables_only:
1990 if not result['you_are_responsible']:
1991 do_not_include = True
1992 break
1993 if do_not_include:
1994 continue
1995
1996 self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True)
1997
1998 self.EndBatch()
1999 wx.EndBusyCursor()
2000
2001
2003 self.empty_grid()
2004 if self.__patient is None:
2005 return
2006
2007 if self.__show_by_panel:
2008 if self.__panel_to_show is None:
2009 return
2010 tests = self.__panel_to_show.get_test_types_for_results (
2011 self.__patient.ID,
2012 order_by = 'unified_abbrev',
2013 unique_meta_types = True
2014 )
2015 self.__repopulate_grid (
2016 tests4rows = tests,
2017 test_pks2show = [ tt['pk_test_type'] for tt in self.__panel_to_show['test_types'] ]
2018 )
2019 return
2020
2021 emr = self.__patient.emr
2022 tests = emr.get_test_types_for_results(order_by = 'unified_abbrev', unique_meta_types = True)
2023 self.__repopulate_grid(tests4rows = tests)
2024
2025
2027
2028 if len(tests4rows) == 0:
2029 return
2030
2031 emr = self.__patient.emr
2032
2033 self.__row_label_data = tests4rows
2034 row_labels = [ '%s%s' % (
2035 gmTools.bool2subst(test_type['is_fake_meta_type'], '', gmTools.u_sum, ''),
2036 test_type['unified_abbrev']
2037 ) for test_type in self.__row_label_data
2038 ]
2039
2040 self.__col_label_data = [ d['clin_when_day'] for d in emr.get_dates_for_results (
2041 tests = test_pks2show,
2042 reverse_chronological = True
2043 )]
2044 col_labels = [ gmDateTime.pydt_strftime(date, self.__date_format, accuracy = gmDateTime.acc_days) for date in self.__col_label_data ]
2045
2046 results = emr.get_test_results_by_date (
2047 tests = test_pks2show,
2048 reverse_chronological = True
2049 )
2050
2051 self.BeginBatch()
2052
2053
2054 self.AppendRows(numRows = len(row_labels))
2055 for row_idx in range(len(row_labels)):
2056 self.SetRowLabelValue(row_idx, row_labels[row_idx])
2057
2058
2059 self.AppendCols(numCols = len(col_labels))
2060 for col_idx in range(len(col_labels)):
2061 self.SetColLabelValue(col_idx, col_labels[col_idx])
2062
2063
2064 for result in results:
2065 row_idx = row_labels.index('%s%s' % (
2066 gmTools.bool2subst(result['is_fake_meta_type'], '', gmTools.u_sum, ''),
2067 result['unified_abbrev']
2068 ))
2069 col_idx = col_labels.index(gmDateTime.pydt_strftime(result['clin_when'], self.__date_format, accuracy = gmDateTime.acc_days))
2070
2071 try:
2072 self.__cell_data[col_idx]
2073 except KeyError:
2074 self.__cell_data[col_idx] = {}
2075
2076
2077 if row_idx in self.__cell_data[col_idx]:
2078 self.__cell_data[col_idx][row_idx].append(result)
2079 self.__cell_data[col_idx][row_idx].sort(key = lambda x: x['clin_when'], reverse = True)
2080 else:
2081 self.__cell_data[col_idx][row_idx] = [result]
2082
2083
2084 vals2display = []
2085 cell_has_out_of_bounds_value = False
2086 for sub_result in self.__cell_data[col_idx][row_idx]:
2087
2088 if sub_result.is_considered_abnormal:
2089 cell_has_out_of_bounds_value = True
2090
2091 abnormality_indicator = sub_result.formatted_abnormality_indicator
2092 if abnormality_indicator is None:
2093 abnormality_indicator = ''
2094 if abnormality_indicator != '':
2095 abnormality_indicator = ' (%s)' % abnormality_indicator[:3]
2096
2097 missing_review = False
2098
2099
2100 if not sub_result['reviewed']:
2101 missing_review = True
2102
2103 else:
2104
2105 if sub_result['you_are_responsible'] and not sub_result['review_by_you']:
2106 missing_review = True
2107
2108 needs_superscript = False
2109
2110
2111 if sub_result.is_long_text:
2112 lines = gmTools.strip_empty_lines (
2113 text = sub_result['unified_val'],
2114 eol = '\n',
2115 return_list = True
2116 )
2117 needs_superscript = True
2118 tmp = lines[0][:7]
2119 else:
2120 val = gmTools.strip_empty_lines (
2121 text = sub_result['unified_val'],
2122 eol = '\n',
2123 return_list = False
2124 ).replace('\n', '//')
2125 if len(val) > 8:
2126 needs_superscript = True
2127 tmp = val[:7]
2128 else:
2129 tmp = '%.8s' % val[:8]
2130
2131
2132 tmp = '%s%.6s' % (tmp, abnormality_indicator)
2133
2134
2135 has_sub_result_comment = gmTools.coalesce (
2136 gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']),
2137 ''
2138 ).strip() != ''
2139 if has_sub_result_comment:
2140 needs_superscript = True
2141
2142 if needs_superscript:
2143 tmp = '%s%s' % (tmp, gmTools.u_superscript_one)
2144
2145
2146 if missing_review:
2147 tmp = '%s %s' % (tmp, gmTools.u_writing_hand)
2148 else:
2149 if sub_result['is_clinically_relevant']:
2150 tmp += ' !'
2151
2152
2153 if len(self.__cell_data[col_idx][row_idx]) > 1:
2154 tmp = '%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp)
2155
2156 vals2display.append(tmp)
2157
2158 self.SetCellValue(row_idx, col_idx, '\n'.join(vals2display))
2159 self.SetCellAlignment(row_idx, col_idx, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170 if cell_has_out_of_bounds_value:
2171
2172 self.SetCellBackgroundColour(row_idx, col_idx, 'PALE TURQUOISE')
2173
2174 self.EndBatch()
2175
2176 self.AutoSize()
2177 self.AdjustScrollbars()
2178 self.ForceRefresh()
2179
2180
2181
2182 return
2183
2184
2186 self.BeginBatch()
2187 self.ClearGrid()
2188
2189
2190 if self.GetNumberRows() > 0:
2191 self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
2192 if self.GetNumberCols() > 0:
2193 self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
2194 self.EndBatch()
2195 self.__cell_data = {}
2196 self.__row_label_data = []
2197 self.__col_label_data = []
2198
2199
2217
2218
2244
2245
2246
2247
2249
2250 self.SetMinSize((10, 10))
2251
2252 self.CreateGrid(0, 1)
2253 self.EnableEditing(0)
2254 self.EnableDragGridSize(1)
2255
2256
2257
2258
2259
2260
2261 self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE)
2262
2263 self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE)
2264 font = self.GetLabelFont()
2265 font.SetWeight(wx.FONTWEIGHT_LIGHT)
2266 self.SetLabelFont(font)
2267
2268
2269 dbcfg = gmCfg.cCfgSQL()
2270 url = dbcfg.get2 (
2271 option = 'external.urls.measurements_encyclopedia',
2272 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
2273 bias = 'user',
2274 default = gmPathLab.URL_test_result_information
2275 )
2276
2277 self.__WIN_corner = self.GetGridCornerLabelWindow()
2278
2279 LNK_lab = wxh.HyperlinkCtrl (
2280 self.__WIN_corner,
2281 -1,
2282 label = _('Tests'),
2283 style = wxh.HL_DEFAULT_STYLE
2284 )
2285 LNK_lab.SetURL(url)
2286 LNK_lab.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND))
2287 LNK_lab.SetToolTip(_(
2288 'Navigate to an encyclopedia of measurements\n'
2289 'and test methods on the web.\n'
2290 '\n'
2291 ' <%s>'
2292 ) % url)
2293
2294 SZR_inner = wx.BoxSizer(wx.HORIZONTAL)
2295 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)
2296 SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0)
2297 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)
2298
2299 SZR_corner = wx.BoxSizer(wx.VERTICAL)
2300 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)
2301 SZR_corner.Add(SZR_inner, 0, wx.EXPAND)
2302 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)
2303
2304 self.__WIN_corner.SetSizer(SZR_corner)
2305 SZR_corner.Fit(self.__WIN_corner)
2306
2307
2309 self.__WIN_corner.Layout()
2310
2311
2312 - def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
2313 """List of <cells> must be in row / col order."""
2314 data = []
2315 for row, col in cells:
2316 try:
2317
2318 data_list = self.__cell_data[col][row]
2319 except KeyError:
2320 continue
2321
2322 if len(data_list) == 1:
2323 data.append(data_list[0])
2324 continue
2325
2326 if exclude_multi_cells:
2327 gmDispatcher.send(signal = 'statustext', msg = _('Excluding multi-result field from further processing.'))
2328 continue
2329
2330 if auto_include_multi_cells:
2331 data.extend(data_list)
2332 continue
2333
2334 data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list)
2335 if data_to_include is None:
2336 continue
2337 data.extend(data_to_include)
2338
2339 return data
2340
2341
2343 data = gmListWidgets.get_choices_from_list (
2344 parent = self,
2345 msg = _(
2346 'Your selection includes a field with multiple results.\n'
2347 '\n'
2348 'Please select the individual results you want to work on:'
2349 ),
2350 caption = _('Selecting test results'),
2351 choices = [ [d['clin_when'], '%s: %s' % (d['abbrev_tt'], d['name_tt']), d['unified_val']] for d in cell_data ],
2352 columns = [ _('Date / Time'), _('Test'), _('Result') ],
2353 data = cell_data,
2354 single_selection = single_selection
2355 )
2356 return data
2357
2358
2359
2360
2362
2363 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
2364 self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels)
2365
2366
2367
2368 self.Bind(wx.EVT_SIZE, self.__resize_corner_window)
2369
2370
2371 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
2372
2373
2375 col = evt.GetCol()
2376 row = evt.GetRow()
2377
2378 try:
2379 self.__cell_data[col][row]
2380 except KeyError:
2381 presets = {}
2382 col_date = self.__col_label_data[col]
2383 presets['clin_when'] = {'data': col_date}
2384 test_type = self.__row_label_data[row]
2385 if test_type['pk_meta_test_type'] is not None:
2386 temporally_closest_result_of_row_type = test_type.meta_test_type.get_temporally_closest_result(col_date, self.__patient.ID)
2387 if temporally_closest_result_of_row_type is not None:
2388
2389
2390 presets['pk_test_type'] = {'data': temporally_closest_result_of_row_type['pk_test_type']}
2391
2392
2393
2394 same_day_results = gmPathLab.get_results_for_day (
2395 timestamp = col_date,
2396 patient = self.__patient.ID,
2397 order_by = None
2398 )
2399 if len(same_day_results) > 0:
2400
2401
2402 presets['pk_episode'] = {'data': same_day_results[0]['pk_episode']}
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412 edit_measurement (
2413 parent = self,
2414 measurement = None,
2415 single_entry = True,
2416 presets = presets
2417 )
2418 return
2419
2420 if len(self.__cell_data[col][row]) > 1:
2421 data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True)
2422 else:
2423 data = self.__cell_data[col][row][0]
2424
2425 if data is None:
2426 return
2427
2428 edit_measurement(parent = self, measurement = data, single_entry = True)
2429
2430
2431
2432
2433
2434
2435
2436
2438
2439
2440
2441 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2442
2443 row = self.YToRow(y)
2444
2445 if self.__prev_label_row == row:
2446 return
2447
2448 self.__prev_label_row == row
2449
2450 evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row))
2451
2452
2453
2454
2455
2456
2457
2458
2460 """Calculate where the mouse is and set the tooltip dynamically."""
2461
2462
2463
2464 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478 row, col = self.XYToCell(x, y)
2479
2480 if (row == self.__prev_row) and (col == self.__prev_col):
2481 return
2482
2483 self.__prev_row = row
2484 self.__prev_col = col
2485
2486 evt.GetEventObject().SetToolTip(self.get_cell_tooltip(col=col, row=row))
2487
2488
2489
2490
2492 return self.__patient
2493
2497
2498 patient = property(_get_patient, _set_patient)
2499
2503
2504 panel_to_show = property(lambda x:x, _set_panel_to_show)
2505
2509
2510 show_by_panel = property(lambda x:x, _set_show_by_panel)
2511
2512
2513
2514
2515 from Gnumed.wxGladeWidgets import wxgMeasurementsPnl
2516
2517 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
2518 """Panel holding a grid with lab data. Used as notebook page."""
2519
2527
2528
2529
2531 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
2532 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
2533 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._schedule_data_reget)
2534 gmDispatcher.connect(signal = 'clin.reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
2535
2537 self._schedule_data_reget()
2538
2540 self._GRID_results_all.patient = None
2541 self._GRID_results_battery.patient = None
2542
2545
2549
2553
2556
2562
2565
2585
2588
2591
2594
2596 wx.CallAfter(self.__on_panel_selected, panel=panel)
2597
2599 if panel is None:
2600 self._TCTRL_panel_comment.SetValue('')
2601 self._GRID_results_battery.panel_to_show = None
2602
2603 self._PNL_results_battery_grid.Hide()
2604 else:
2605 pnl = self._PRW_panel.GetData(as_instance = True)
2606 self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
2607 pnl['comment'],
2608 ''
2609 ))
2610 self._GRID_results_battery.panel_to_show = pnl
2611
2612 self._PNL_results_battery_grid.Show()
2613 self._GRID_results_battery.Fit()
2614 self._GRID_results_all.Fit()
2615 self.Layout()
2616
2618 wx.CallAfter(self.__on_panel_selection_modified)
2619
2621 self._TCTRL_panel_comment.SetValue('')
2622 if self._PRW_panel.GetValue().strip() == '':
2623 self._GRID_results_battery.panel_to_show = None
2624
2625 self._PNL_results_battery_grid.Hide()
2626 self.Layout()
2627
2628
2629
2631 self.SetMinSize((10, 10))
2632
2633 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
2634
2635 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
2636 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
2637
2638 item = self.__action_button_popup.Append(-1, _('Plot'))
2639 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
2640
2641 item = self.__action_button_popup.Append(-1, _('Export to &file'))
2642 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item)
2643 self.__action_button_popup.Enable(id = menu_id, enable = False)
2644
2645 item = self.__action_button_popup.Append(-1, _('Export to &clipboard'))
2646 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item)
2647 self.__action_button_popup.Enable(id = menu_id, enable = False)
2648
2649 item = self.__action_button_popup.Append(-1, _('&Delete'))
2650 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
2651
2652
2653
2654
2655 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
2656 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
2657
2658 self._GRID_results_battery.show_by_panel = True
2659 self._GRID_results_battery.panel_to_show = None
2660
2661 self._PNL_results_battery_grid.Hide()
2662 self._BTN_display_mode.SetLabel(_('All: by &Day'))
2663
2664 self._PNL_results_all_grid.Show()
2665 self._PNL_results_all_listed.Hide()
2666 self.Layout()
2667
2668 self._PRW_panel.SetFocus()
2669
2670
2671
2673 pat = gmPerson.gmCurrentPatient()
2674 if pat.connected:
2675 self._GRID_results_battery.patient = pat
2676 if self.__display_mode == 'grid':
2677 self._GRID_results_all.patient = pat
2678 self._PNL_results_all_listed.patient = None
2679 else:
2680 self._GRID_results_all.patient = None
2681 self._PNL_results_all_listed.patient = pat
2682 else:
2683 self._GRID_results_battery.patient = None
2684 self._GRID_results_all.patient = None
2685 self._PNL_results_all_listed.patient = None
2686 return True
2687
2688
2689
2690
2692
2693 if tests is None:
2694 return True
2695
2696 if len(tests) == 0:
2697 return True
2698
2699 if parent is None:
2700 parent = wx.GetApp().GetTopWindow()
2701
2702 if len(tests) > 10:
2703 test_count = len(tests)
2704 tests2show = None
2705 else:
2706 test_count = None
2707 tests2show = tests
2708 if len(tests) == 0:
2709 return True
2710
2711 dlg = cMeasurementsReviewDlg(parent, -1, tests = tests, test_count = test_count)
2712 decision = dlg.ShowModal()
2713 if decision != wx.ID_APPLY:
2714 return True
2715
2716 wx.BeginBusyCursor()
2717 if dlg._RBTN_confirm_abnormal.GetValue():
2718 abnormal = None
2719 elif dlg._RBTN_results_normal.GetValue():
2720 abnormal = False
2721 else:
2722 abnormal = True
2723
2724 if dlg._RBTN_confirm_relevance.GetValue():
2725 relevant = None
2726 elif dlg._RBTN_results_not_relevant.GetValue():
2727 relevant = False
2728 else:
2729 relevant = True
2730
2731 comment = None
2732 if len(tests) == 1:
2733 comment = dlg._TCTRL_comment.GetValue()
2734
2735 make_responsible = dlg._CHBOX_responsible.IsChecked()
2736 dlg.DestroyLater()
2737
2738 for test in tests:
2739 test.set_review (
2740 technically_abnormal = abnormal,
2741 clinically_relevant = relevant,
2742 comment = comment,
2743 make_me_responsible = make_responsible
2744 )
2745 wx.EndBusyCursor()
2746
2747 return True
2748
2749
2750 from Gnumed.wxGladeWidgets import wxgMeasurementsReviewDlg
2751
2753
2755
2756 try:
2757 tests = kwargs['tests']
2758 del kwargs['tests']
2759 test_count = len(tests)
2760 try: del kwargs['test_count']
2761 except KeyError: pass
2762 except KeyError:
2763 tests = None
2764 test_count = kwargs['test_count']
2765 del kwargs['test_count']
2766
2767 wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs)
2768
2769 if tests is None:
2770 msg = _('%s results selected. Too many to list individually.') % test_count
2771 else:
2772 msg = '\n'.join (
2773 [ '%s: %s %s (%s)' % (
2774 t['unified_abbrev'],
2775 t['unified_val'],
2776 t['val_unit'],
2777 gmDateTime.pydt_strftime(t['clin_when'], '%Y %b %d')
2778 ) for t in tests
2779 ]
2780 )
2781
2782 self._LBL_tests.SetLabel(msg)
2783
2784 if test_count == 1:
2785 self._TCTRL_comment.Enable(True)
2786 self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], ''))
2787 if tests[0]['you_are_responsible']:
2788 self._CHBOX_responsible.Enable(False)
2789
2790 self.Fit()
2791
2792
2793
2799
2800
2801 from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl
2802
2803 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
2804 """This edit area saves *new* measurements into the active patient only."""
2805
2822
2823
2824
2825
2827 try:
2828 self._PRW_test.SetData(data = fields['pk_test_type']['data'])
2829 except KeyError:
2830 pass
2831 try:
2832 self._DPRW_evaluated.SetData(data = fields['clin_when']['data'])
2833 except KeyError:
2834 pass
2835 try:
2836 self._PRW_problem.SetData(data = fields['pk_episode']['data'])
2837 except KeyError:
2838 pass
2839 try:
2840 self._PRW_units.SetText(fields['val_unit']['data'], fields['val_unit']['data'], True)
2841 except KeyError:
2842 pass
2843 try:
2844 self._TCTRL_normal_min.SetValue(fields['val_normal_min']['data'])
2845 except KeyError:
2846 pass
2847 try:
2848 self._TCTRL_normal_max.SetValue(fields['val_normal_max']['data'])
2849 except KeyError:
2850 pass
2851 try:
2852 self._TCTRL_normal_range.SetValue(fields['val_normal_range']['data'])
2853 except KeyError:
2854 pass
2855 try:
2856 self._TCTRL_target_min.SetValue(fields['val_target_min']['data'])
2857 except KeyError:
2858 pass
2859 try:
2860 self._TCTRL_target_max.SetValue(fields['val_target_max']['data'])
2861 except KeyError:
2862 pass
2863 try:
2864 self._TCTRL_target_range.SetValue(fields['val_target_range']['data'])
2865 except KeyError:
2866 pass
2867
2868 self._TCTRL_result.SetFocus()
2869
2870
2902
2904 self._PRW_test.SetData(data = self.data['pk_test_type'])
2905 self.__refresh_loinc_info()
2906 self.__refresh_previous_value()
2907 self.__update_units_context()
2908 self._TCTRL_result.SetValue(self.data['unified_val'])
2909 self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True)
2910 self._PRW_abnormality_indicator.SetText (
2911 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2912 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2913 True
2914 )
2915 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2916 self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], ''))
2917 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2918 self._PRW_problem.SetData(self.data['pk_episode'])
2919 self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], ''))
2920 self._CHBOX_review.SetValue(False)
2921 self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False))
2922 self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False))
2923 self._CHBOX_abnormal.Enable(False)
2924 self._CHBOX_relevant.Enable(False)
2925 self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], ''))
2926 self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(self.data['val_normal_min'], '')))
2927 self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(self.data['val_normal_max'], '')))
2928 self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], ''))
2929 self._TCTRL_target_min.SetValue(str(gmTools.coalesce(self.data['val_target_min'], '')))
2930 self._TCTRL_target_max.SetValue(str(gmTools.coalesce(self.data['val_target_max'], '')))
2931 self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], ''))
2932 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], ''))
2933
2934 self._TCTRL_result.SetFocus()
2935
2937 self._PRW_test.SetText('', None, True)
2938 self.__refresh_loinc_info()
2939 self.__refresh_previous_value()
2940 self.__update_units_context()
2941 self._TCTRL_result.SetValue('')
2942 self._PRW_units.SetText('', None, True)
2943 self._PRW_abnormality_indicator.SetText('', None, True)
2944 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2945 self._TCTRL_note_test_org.SetValue('')
2946 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2947 self._PRW_problem.SetData(self.data['pk_episode'])
2948 self._TCTRL_narrative.SetValue('')
2949 self._CHBOX_review.SetValue(False)
2950 self._CHBOX_abnormal.SetValue(False)
2951 self._CHBOX_relevant.SetValue(False)
2952 self._CHBOX_abnormal.Enable(False)
2953 self._CHBOX_relevant.Enable(False)
2954 self._TCTRL_review_comment.SetValue('')
2955 self._TCTRL_normal_min.SetValue('')
2956 self._TCTRL_normal_max.SetValue('')
2957 self._TCTRL_normal_range.SetValue('')
2958 self._TCTRL_target_min.SetValue('')
2959 self._TCTRL_target_max.SetValue('')
2960 self._TCTRL_target_range.SetValue('')
2961 self._TCTRL_norm_ref_group.SetValue('')
2962
2963 self._PRW_test.SetFocus()
2964
2966
2967 validity = True
2968
2969 if not self._DPRW_evaluated.is_valid_timestamp():
2970 self._DPRW_evaluated.display_as_valid(False)
2971 validity = False
2972 else:
2973 self._DPRW_evaluated.display_as_valid(True)
2974
2975 val = self._TCTRL_result.GetValue().strip()
2976 if val == '':
2977 validity = False
2978 self.display_ctrl_as_valid(self._TCTRL_result, False)
2979 else:
2980 self.display_ctrl_as_valid(self._TCTRL_result, True)
2981 numeric, val = gmTools.input2decimal(val)
2982 if numeric:
2983 if self._PRW_units.GetValue().strip() == '':
2984 self._PRW_units.display_as_valid(False)
2985 validity = False
2986 else:
2987 self._PRW_units.display_as_valid(True)
2988 else:
2989 self._PRW_units.display_as_valid(True)
2990
2991 if self._PRW_problem.GetValue().strip() == '':
2992 self._PRW_problem.display_as_valid(False)
2993 validity = False
2994 else:
2995 self._PRW_problem.display_as_valid(True)
2996
2997 if self._PRW_test.GetValue().strip() == '':
2998 self._PRW_test.display_as_valid(False)
2999 validity = False
3000 else:
3001 self._PRW_test.display_as_valid(True)
3002
3003 if self._PRW_intended_reviewer.GetData() is None:
3004 self._PRW_intended_reviewer.display_as_valid(False)
3005 validity = False
3006 else:
3007 self._PRW_intended_reviewer.display_as_valid(True)
3008
3009 ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max]
3010 for widget in ctrls:
3011 val = widget.GetValue().strip()
3012 if val == '':
3013 continue
3014 try:
3015 decimal.Decimal(val.replace(',', '.', 1))
3016 self.display_ctrl_as_valid(widget, True)
3017 except:
3018 validity = False
3019 self.display_ctrl_as_valid(widget, False)
3020
3021 if validity is False:
3022 self.StatusText = _('Cannot save result. Invalid or missing essential input.')
3023
3024 return validity
3025
3027
3028 emr = gmPerson.gmCurrentPatient().emr
3029
3030 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
3031 if success:
3032 v_num = result
3033 v_al = None
3034 else:
3035 v_al = self._TCTRL_result.GetValue().strip()
3036 v_num = None
3037
3038 pk_type = self._PRW_test.GetData()
3039 if pk_type is None:
3040 abbrev = self._PRW_test.GetValue().strip()
3041 name = self._PRW_test.GetValue().strip()
3042 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3043 lab = manage_measurement_orgs (
3044 parent = self,
3045 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
3046 )
3047 if lab is not None:
3048 lab = lab['pk_test_org']
3049 tt = gmPathLab.create_measurement_type (
3050 lab = lab,
3051 abbrev = abbrev,
3052 name = name,
3053 unit = unit
3054 )
3055 pk_type = tt['pk_test_type']
3056
3057 tr = emr.add_test_result (
3058 episode = self._PRW_problem.GetData(can_create=True, is_open=False),
3059 type = pk_type,
3060 intended_reviewer = self._PRW_intended_reviewer.GetData(),
3061 val_num = v_num,
3062 val_alpha = v_al,
3063 unit = self._PRW_units.GetValue()
3064 )
3065
3066 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
3067
3068 ctrls = [
3069 ('abnormality_indicator', self._PRW_abnormality_indicator),
3070 ('note_test_org', self._TCTRL_note_test_org),
3071 ('comment', self._TCTRL_narrative),
3072 ('val_normal_range', self._TCTRL_normal_range),
3073 ('val_target_range', self._TCTRL_target_range),
3074 ('norm_ref_group', self._TCTRL_norm_ref_group)
3075 ]
3076 for field, widget in ctrls:
3077 tr[field] = widget.GetValue().strip()
3078
3079 ctrls = [
3080 ('val_normal_min', self._TCTRL_normal_min),
3081 ('val_normal_max', self._TCTRL_normal_max),
3082 ('val_target_min', self._TCTRL_target_min),
3083 ('val_target_max', self._TCTRL_target_max)
3084 ]
3085 for field, widget in ctrls:
3086 val = widget.GetValue().strip()
3087 if val == '':
3088 tr[field] = None
3089 else:
3090 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
3091
3092 tr.save_payload()
3093
3094 if self._CHBOX_review.GetValue() is True:
3095 tr.set_review (
3096 technically_abnormal = self._CHBOX_abnormal.GetValue(),
3097 clinically_relevant = self._CHBOX_relevant.GetValue(),
3098 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
3099 make_me_responsible = False
3100 )
3101
3102 self.data = tr
3103
3104
3105
3106
3107
3108
3109
3110
3111 return True
3112
3114
3115 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
3116 if success:
3117 v_num = result
3118 v_al = None
3119 else:
3120 v_num = None
3121 v_al = self._TCTRL_result.GetValue().strip()
3122
3123 pk_type = self._PRW_test.GetData()
3124 if pk_type is None:
3125 abbrev = self._PRW_test.GetValue().strip()
3126 name = self._PRW_test.GetValue().strip()
3127 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3128 lab = manage_measurement_orgs (
3129 parent = self,
3130 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
3131 )
3132 if lab is not None:
3133 lab = lab['pk_test_org']
3134 tt = gmPathLab.create_measurement_type (
3135 lab = None,
3136 abbrev = abbrev,
3137 name = name,
3138 unit = unit
3139 )
3140 pk_type = tt['pk_test_type']
3141
3142 tr = self.data
3143
3144 tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False)
3145 tr['pk_test_type'] = pk_type
3146 tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData()
3147 tr['val_num'] = v_num
3148 tr['val_alpha'] = v_al
3149 tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3150 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
3151
3152 ctrls = [
3153 ('abnormality_indicator', self._PRW_abnormality_indicator),
3154 ('note_test_org', self._TCTRL_note_test_org),
3155 ('comment', self._TCTRL_narrative),
3156 ('val_normal_range', self._TCTRL_normal_range),
3157 ('val_target_range', self._TCTRL_target_range),
3158 ('norm_ref_group', self._TCTRL_norm_ref_group)
3159 ]
3160 for field, widget in ctrls:
3161 tr[field] = widget.GetValue().strip()
3162
3163 ctrls = [
3164 ('val_normal_min', self._TCTRL_normal_min),
3165 ('val_normal_max', self._TCTRL_normal_max),
3166 ('val_target_min', self._TCTRL_target_min),
3167 ('val_target_max', self._TCTRL_target_max)
3168 ]
3169 for field, widget in ctrls:
3170 val = widget.GetValue().strip()
3171 if val == '':
3172 tr[field] = None
3173 else:
3174 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
3175
3176 tr.save_payload()
3177
3178 if self._CHBOX_review.GetValue() is True:
3179 tr.set_review (
3180 technically_abnormal = self._CHBOX_abnormal.GetValue(),
3181 clinically_relevant = self._CHBOX_relevant.GetValue(),
3182 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
3183 make_me_responsible = False
3184 )
3185
3186
3187
3188
3189
3190
3191
3192
3193 return True
3194
3195
3196
3201
3203 self.__refresh_loinc_info()
3204 self.__refresh_previous_value()
3205 self.__update_units_context()
3206
3207 self.__update_normal_range()
3208 self.__update_clinical_range()
3209
3211
3212 self.__update_normal_range()
3213 self.__update_clinical_range()
3214
3216
3217 if not self._CHBOX_review.GetValue():
3218 self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != '')
3219
3224
3240
3244
3245
3246
3248
3249 if self._PRW_test.GetData() is None:
3250 self._PRW_units.unset_context(context = 'pk_type')
3251 self._PRW_units.unset_context(context = 'loinc')
3252 if self._PRW_test.GetValue().strip() == '':
3253 self._PRW_units.unset_context(context = 'test_name')
3254 else:
3255 self._PRW_units.set_context(context = 'test_name', val = self._PRW_test.GetValue().strip())
3256 return
3257
3258 tt = self._PRW_test.GetData(as_instance = True)
3259
3260 self._PRW_units.set_context(context = 'pk_type', val = tt['pk_test_type'])
3261 self._PRW_units.set_context(context = 'test_name', val = tt['name'])
3262
3263 if tt['loinc'] is not None:
3264 self._PRW_units.set_context(context = 'loinc', val = tt['loinc'])
3265
3266
3267 if self._PRW_units.GetValue().strip() == '':
3268 clin_when = self._DPRW_evaluated.GetData()
3269 if clin_when is None:
3270 unit = tt.temporally_closest_unit
3271 else:
3272 clin_when = clin_when.get_pydt()
3273 unit = tt.get_temporally_closest_unit(timestamp = clin_when)
3274 if unit is None:
3275 self._PRW_units.SetText('', unit, True)
3276 else:
3277 self._PRW_units.SetText(unit, unit, True)
3278
3279
3300
3301
3322
3323
3325
3326 self._TCTRL_loinc.SetValue('')
3327
3328 if self._PRW_test.GetData() is None:
3329 return
3330
3331 tt = self._PRW_test.GetData(as_instance = True)
3332
3333 if tt['loinc'] is None:
3334 return
3335
3336 info = gmLOINC.loinc2term(loinc = tt['loinc'])
3337 if len(info) == 0:
3338 self._TCTRL_loinc.SetValue('')
3339 return
3340
3341 self._TCTRL_loinc.SetValue('%s: %s' % (tt['loinc'], info[0]))
3342
3343
3345 self._TCTRL_previous_value.SetValue('')
3346
3347
3348 if self.data is not None:
3349 return
3350
3351 if self._PRW_test.GetData() is None:
3352 return
3353
3354 tt = self._PRW_test.GetData(as_instance = True)
3355 most_recent_results = tt.get_most_recent_results (
3356 max_no_of_results = 1,
3357 patient = gmPerson.gmCurrentPatient().ID
3358 )
3359 if len(most_recent_results) == 0:
3360 return
3361
3362 most_recent = most_recent_results[0]
3363 self._TCTRL_previous_value.SetValue(_('%s ago: %s%s%s - %s%s') % (
3364 gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - most_recent['clin_when']),
3365 most_recent['unified_val'],
3366 most_recent['val_unit'],
3367 gmTools.coalesce(most_recent['abnormality_indicator'], '', ' (%s)'),
3368 most_recent['abbrev_tt'],
3369 gmTools.coalesce(most_recent.formatted_range, '', ' [%s]')
3370 ))
3371 self._TCTRL_previous_value.SetToolTip(most_recent.format (
3372 with_review = True,
3373 with_evaluation = False,
3374 with_ranges = True,
3375 with_episode = True,
3376 with_type_details=True
3377 ))
3378
3379
3380
3381
3383
3384 if parent is None:
3385 parent = wx.GetApp().GetTopWindow()
3386
3387 if msg is None:
3388 msg = _('Pick the relevant measurement types.')
3389
3390 if right_column is None:
3391 right_columns = [_('Picked')]
3392 else:
3393 right_columns = [right_column]
3394
3395 picker = gmListWidgets.cItemPickerDlg(parent, -1, msg = msg)
3396 picker.set_columns(columns = [_('Known measurement types')], columns_right = right_columns)
3397 types = gmPathLab.get_measurement_types(order_by = 'unified_abbrev')
3398 picker.set_choices (
3399 choices = [
3400 '%s: %s%s' % (
3401 t['unified_abbrev'],
3402 t['unified_name'],
3403 gmTools.coalesce(t['name_org'], '', ' (%s)')
3404 )
3405 for t in types
3406 ],
3407 data = types
3408 )
3409 if picks is not None:
3410 picker.set_picks (
3411 picks = [
3412 '%s: %s%s' % (
3413 p['unified_abbrev'],
3414 p['unified_name'],
3415 gmTools.coalesce(p['name_org'], '', ' (%s)')
3416 )
3417 for p in picks
3418 ],
3419 data = picks
3420 )
3421 result = picker.ShowModal()
3422
3423 if result == wx.ID_CANCEL:
3424 picker.DestroyLater()
3425 return None
3426
3427 picks = picker.picks
3428 picker.DestroyLater()
3429 return picks
3430
3431
3454
3455
3456 def delete(measurement_type):
3457 if measurement_type.in_use:
3458 gmDispatcher.send (
3459 signal = 'statustext',
3460 beep = True,
3461 msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev'])
3462 )
3463 return False
3464 gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type'])
3465 return True
3466
3467
3468 def get_tooltip(test_type):
3469 return test_type.format()
3470
3471
3472 def manage_aggregates(test_type):
3473 manage_meta_test_types(parent = parent)
3474 return False
3475
3476
3477 def manage_panels_of_type(test_type):
3478 if test_type['loinc'] is None:
3479 return False
3480 all_panels = gmPathLab.get_test_panels(order_by = 'description')
3481 curr_panels = test_type.test_panels
3482 if curr_panels is None:
3483 curr_panels = []
3484 panel_candidates = [ p for p in all_panels if p['pk_test_panel'] not in [
3485 c_pnl['pk_test_panel'] for c_pnl in curr_panels
3486 ] ]
3487 picker = gmListWidgets.cItemPickerDlg(parent, -1, title = 'Panels with [%s]' % test_type['abbrev'])
3488 picker.set_columns(['Panels available'], ['Panels [%s] is to be on' % test_type['abbrev']])
3489 picker.set_choices (
3490 choices = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in panel_candidates ],
3491 data = panel_candidates
3492 )
3493 picker.set_picks (
3494 picks = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in curr_panels ],
3495 data = curr_panels
3496 )
3497 exit_type = picker.ShowModal()
3498 if exit_type == wx.ID_CANCEL:
3499 return False
3500
3501
3502 panels2add = [ p for p in picker.picks if p['pk_test_panel'] not in [
3503 c_pnl['pk_test_panel'] for c_pnl in curr_panels
3504 ] ]
3505
3506 panels2remove = [ p for p in curr_panels if p['pk_test_panel'] not in [
3507 picked_pnl['pk_test_panel'] for picked_pnl in picker.picks
3508 ] ]
3509 for new_panel in panels2add:
3510 new_panel.add_loinc(test_type['loinc'])
3511 for stale_panel in panels2remove:
3512 stale_panel.remove_loinc(test_type['loinc'])
3513
3514 return True
3515
3516
3517 def refresh(lctrl):
3518 mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev')
3519 items = [ [
3520 m['abbrev'],
3521 m['name'],
3522 gmTools.coalesce(m['reference_unit'], ''),
3523 gmTools.coalesce(m['loinc'], ''),
3524 gmTools.coalesce(m['comment_type'], ''),
3525 gmTools.coalesce(m['name_org'], '?'),
3526 gmTools.coalesce(m['comment_org'], ''),
3527 m['pk_test_type']
3528 ] for m in mtypes ]
3529 lctrl.set_string_items(items)
3530 lctrl.set_data(mtypes)
3531
3532
3533 gmListWidgets.get_choices_from_list (
3534 parent = parent,
3535 caption = _('Measurement types.'),
3536 columns = [ _('Abbrev'), _('Name'), _('Unit'), _('LOINC'), _('Comment'), _('Org'), _('Comment'), '#' ],
3537 single_selection = True,
3538 refresh_callback = refresh,
3539 edit_callback = edit,
3540 new_callback = edit,
3541 delete_callback = delete,
3542 list_tooltip_callback = get_tooltip,
3543 left_extra_button = (_('%s &Aggregate') % gmTools.u_sum, _('Manage aggregations (%s) of tests into groups.') % gmTools.u_sum, manage_aggregates),
3544 middle_extra_button = (_('Select panels'), _('Select panels the focussed test type is to belong to.'), manage_panels_of_type)
3545 )
3546
3547
3549
3551
3552 query = """
3553 SELECT DISTINCT ON (field_label)
3554 pk_test_type AS data,
3555 name
3556 || ' ('
3557 || coalesce (
3558 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
3559 '%(in_house)s'
3560 )
3561 || ')'
3562 AS field_label,
3563 name
3564 || ' ('
3565 || abbrev || ', '
3566 || coalesce(abbrev_meta || ': ' || name_meta || ', ', '')
3567 || coalesce (
3568 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
3569 '%(in_house)s'
3570 )
3571 || ')'
3572 AS list_label
3573 FROM
3574 clin.v_test_types c_vtt
3575 WHERE
3576 abbrev_meta %%(fragment_condition)s
3577 OR
3578 name_meta %%(fragment_condition)s
3579 OR
3580 abbrev %%(fragment_condition)s
3581 OR
3582 name %%(fragment_condition)s
3583 ORDER BY field_label
3584 LIMIT 50""" % {'in_house': _('generic / in house lab')}
3585
3586 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3587 mp.setThresholds(1, 2, 4)
3588 mp.word_separators = '[ \t:@]+'
3589 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
3590 self.matcher = mp
3591 self.SetToolTip(_('Select the type of measurement.'))
3592 self.selection_only = False
3593
3594
3600
3601
3603 lab = gmPathLab.cTestOrg(aPK_obj = instance['pk_test_org'])
3604 field_label = '%s (%s @ %s)' % (
3605 instance['name'],
3606 lab['unit'],
3607 lab['organization']
3608 )
3609 return self.SetText(value = field_label, data = instance['pk_test_type'])
3610
3611
3614
3615
3618
3619
3620 from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl
3621
3622 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
3623
3640
3641
3643
3644
3645 query = """
3646 select distinct on (name)
3647 pk,
3648 name
3649 from clin.test_type
3650 where
3651 name %(fragment_condition)s
3652 order by name
3653 limit 50"""
3654 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3655 mp.setThresholds(1, 2, 4)
3656 self._PRW_name.matcher = mp
3657 self._PRW_name.selection_only = False
3658 self._PRW_name.add_callback_on_lose_focus(callback = self._on_name_lost_focus)
3659
3660
3661 query = """
3662 select distinct on (abbrev)
3663 pk,
3664 abbrev
3665 from clin.test_type
3666 where
3667 abbrev %(fragment_condition)s
3668 order by abbrev
3669 limit 50"""
3670 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3671 mp.setThresholds(1, 2, 3)
3672 self._PRW_abbrev.matcher = mp
3673 self._PRW_abbrev.selection_only = False
3674
3675
3676 self._PRW_reference_unit.selection_only = False
3677
3678
3679 mp = gmLOINC.cLOINCMatchProvider()
3680 mp.setThresholds(1, 2, 4)
3681
3682
3683 self._PRW_loinc.matcher = mp
3684 self._PRW_loinc.selection_only = False
3685 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
3686
3687
3689
3690 test = self._PRW_name.GetValue().strip()
3691
3692 if test == '':
3693 self._PRW_reference_unit.unset_context(context = 'test_name')
3694 return
3695
3696 self._PRW_reference_unit.set_context(context = 'test_name', val = test)
3697
3698
3700 loinc = self._PRW_loinc.GetData()
3701
3702 if loinc is None:
3703 self._TCTRL_loinc_info.SetValue('')
3704 self._PRW_reference_unit.unset_context(context = 'loinc')
3705 return
3706
3707 self._PRW_reference_unit.set_context(context = 'loinc', val = loinc)
3708
3709 info = gmLOINC.loinc2term(loinc = loinc)
3710 if len(info) == 0:
3711 self._TCTRL_loinc_info.SetValue('')
3712 return
3713
3714 self._TCTRL_loinc_info.SetValue(info[0])
3715
3716
3717
3718
3720
3721 has_errors = False
3722 for field in [self._PRW_name, self._PRW_abbrev, self._PRW_reference_unit]:
3723 if field.GetValue().strip() in ['', None]:
3724 has_errors = True
3725 field.display_as_valid(valid = False)
3726 else:
3727 field.display_as_valid(valid = True)
3728 field.Refresh()
3729
3730 return (not has_errors)
3731
3732
3762
3801
3802
3804 self._PRW_name.SetText('', None, True)
3805 self._on_name_lost_focus()
3806 self._PRW_abbrev.SetText('', None, True)
3807 self._PRW_reference_unit.SetText('', None, True)
3808 self._PRW_loinc.SetText('', None, True)
3809 self._on_loinc_lost_focus()
3810 self._TCTRL_comment_type.SetValue('')
3811 self._PRW_test_org.SetText('', None, True)
3812 self._PRW_meta_type.SetText('', None, True)
3813
3814 self._PRW_name.SetFocus()
3815
3817 self._PRW_name.SetText(self.data['name'], self.data['name'], True)
3818 self._on_name_lost_focus()
3819 self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True)
3820 self._PRW_reference_unit.SetText (
3821 gmTools.coalesce(self.data['reference_unit'], ''),
3822 self.data['reference_unit'],
3823 True
3824 )
3825 self._PRW_loinc.SetText (
3826 gmTools.coalesce(self.data['loinc'], ''),
3827 self.data['loinc'],
3828 True
3829 )
3830 self._on_loinc_lost_focus()
3831 self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], ''))
3832 self._PRW_test_org.SetText (
3833 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
3834 self.data['pk_test_org'],
3835 True
3836 )
3837 if self.data['pk_meta_test_type'] is None:
3838 self._PRW_meta_type.SetText('', None, True)
3839 else:
3840 self._PRW_meta_type.SetText('%s: %s' % (self.data['abbrev_meta'], self.data['name_meta']), self.data['pk_meta_test_type'], True)
3841
3842 self._PRW_name.SetFocus()
3843
3845 self._refresh_as_new()
3846 self._PRW_test_org.SetText (
3847 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
3848 self.data['pk_test_org'],
3849 True
3850 )
3851 self._PRW_name.SetFocus()
3852
3853
3854 _SQL_units_from_test_results = """
3855 -- via clin.v_test_results.pk_type (for types already used in results)
3856 SELECT
3857 val_unit AS data,
3858 val_unit AS field_label,
3859 val_unit || ' (' || name_tt || ')' AS list_label,
3860 1 AS rank
3861 FROM
3862 clin.v_test_results
3863 WHERE
3864 (
3865 val_unit %(fragment_condition)s
3866 OR
3867 reference_unit %(fragment_condition)s
3868 )
3869 %(ctxt_type_pk)s
3870 %(ctxt_test_name)s
3871 """
3872
3873 _SQL_units_from_test_types = """
3874 -- via clin.test_type (for types not yet used in results)
3875 SELECT
3876 reference_unit AS data,
3877 reference_unit AS field_label,
3878 reference_unit || ' (' || name || ')' AS list_label,
3879 2 AS rank
3880 FROM
3881 clin.test_type
3882 WHERE
3883 reference_unit %(fragment_condition)s
3884 %(ctxt_ctt)s
3885 """
3886
3887 _SQL_units_from_loinc_ipcc = """
3888 -- via ref.loinc.ipcc_units
3889 SELECT
3890 ipcc_units AS data,
3891 ipcc_units AS field_label,
3892 ipcc_units || ' (LOINC.ipcc: ' || term || ')' AS list_label,
3893 3 AS rank
3894 FROM
3895 ref.loinc
3896 WHERE
3897 ipcc_units %(fragment_condition)s
3898 %(ctxt_loinc)s
3899 %(ctxt_loinc_term)s
3900 """
3901
3902 _SQL_units_from_loinc_submitted = """
3903 -- via ref.loinc.submitted_units
3904 SELECT
3905 submitted_units AS data,
3906 submitted_units AS field_label,
3907 submitted_units || ' (LOINC.submitted:' || term || ')' AS list_label,
3908 3 AS rank
3909 FROM
3910 ref.loinc
3911 WHERE
3912 submitted_units %(fragment_condition)s
3913 %(ctxt_loinc)s
3914 %(ctxt_loinc_term)s
3915 """
3916
3917 _SQL_units_from_loinc_example = """
3918 -- via ref.loinc.example_units
3919 SELECT
3920 example_units AS data,
3921 example_units AS field_label,
3922 example_units || ' (LOINC.example: ' || term || ')' AS list_label,
3923 3 AS rank
3924 FROM
3925 ref.loinc
3926 WHERE
3927 example_units %(fragment_condition)s
3928 %(ctxt_loinc)s
3929 %(ctxt_loinc_term)s
3930 """
3931
3932 _SQL_units_from_substance_doses = """
3933 -- via ref.v_substance_doses.unit
3934 SELECT
3935 unit AS data,
3936 unit AS field_label,
3937 unit || ' (' || substance || ')' AS list_label,
3938 2 AS rank
3939 FROM
3940 ref.v_substance_doses
3941 WHERE
3942 unit %(fragment_condition)s
3943 %(ctxt_substance)s
3944 """
3945
3946 _SQL_units_from_substance_doses2 = """
3947 -- via ref.v_substance_doses.dose_unit
3948 SELECT
3949 dose_unit AS data,
3950 dose_unit AS field_label,
3951 dose_unit || ' (' || substance || ')' AS list_label,
3952 2 AS rank
3953 FROM
3954 ref.v_substance_doses
3955 WHERE
3956 dose_unit %(fragment_condition)s
3957 %(ctxt_substance)s
3958 """
3959
3960
3962
3964
3965 query = """
3966 SELECT DISTINCT ON (data)
3967 data,
3968 field_label,
3969 list_label
3970 FROM (
3971
3972 SELECT
3973 data,
3974 field_label,
3975 list_label,
3976 rank
3977 FROM (
3978 (%s) UNION ALL
3979 (%s) UNION ALL
3980 (%s) UNION ALL
3981 (%s) UNION ALL
3982 (%s) UNION ALL
3983 (%s) UNION ALL
3984 (%s)
3985 ) AS all_matching_units
3986 WHERE data IS NOT NULL
3987 ORDER BY rank, list_label
3988
3989 ) AS ranked_matching_units
3990 LIMIT 50""" % (
3991 _SQL_units_from_test_results,
3992 _SQL_units_from_test_types,
3993 _SQL_units_from_loinc_ipcc,
3994 _SQL_units_from_loinc_submitted,
3995 _SQL_units_from_loinc_example,
3996 _SQL_units_from_substance_doses,
3997 _SQL_units_from_substance_doses2
3998 )
3999
4000 ctxt = {
4001 'ctxt_type_pk': {
4002 'where_part': 'AND pk_test_type = %(pk_type)s',
4003 'placeholder': 'pk_type'
4004 },
4005 'ctxt_test_name': {
4006 'where_part': 'AND %(test_name)s IN (name_tt, name_meta, abbrev_meta)',
4007 'placeholder': 'test_name'
4008 },
4009 'ctxt_ctt': {
4010 'where_part': 'AND %(test_name)s IN (name, abbrev)',
4011 'placeholder': 'test_name'
4012 },
4013 'ctxt_loinc': {
4014 'where_part': 'AND code = %(loinc)s',
4015 'placeholder': 'loinc'
4016 },
4017 'ctxt_loinc_term': {
4018 'where_part': 'AND term ~* %(test_name)s',
4019 'placeholder': 'test_name'
4020 },
4021 'ctxt_substance': {
4022 'where_part': 'AND description ~* %(substance)s',
4023 'placeholder': 'substance'
4024 }
4025 }
4026
4027 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = ctxt)
4028 mp.setThresholds(1, 2, 4)
4029
4030 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4031 self.matcher = mp
4032 self.SetToolTip(_('Select the desired unit for the amount or measurement.'))
4033 self.selection_only = False
4034 self.phrase_separators = '[;|]+'
4035
4036
4037
4038
4040
4042
4043 query = """
4044 select distinct abnormality_indicator,
4045 abnormality_indicator, abnormality_indicator
4046 from clin.v_test_results
4047 where
4048 abnormality_indicator %(fragment_condition)s
4049 order by abnormality_indicator
4050 limit 25"""
4051
4052 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4053 mp.setThresholds(1, 1, 2)
4054 mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"'
4055 mp.word_separators = '[ \t&:]+'
4056 gmPhraseWheel.cPhraseWheel.__init__ (
4057 self,
4058 *args,
4059 **kwargs
4060 )
4061 self.matcher = mp
4062 self.SetToolTip(_('Select an indicator for the level of abnormality.'))
4063 self.selection_only = False
4064
4065
4066
4067
4079
4088
4089 def refresh(lctrl):
4090 orgs = gmPathLab.get_test_orgs()
4091 lctrl.set_string_items ([
4092 (o['unit'], o['organization'], gmTools.coalesce(o['test_org_contact'], ''), gmTools.coalesce(o['comment'], ''), o['pk_test_org'])
4093 for o in orgs
4094 ])
4095 lctrl.set_data(orgs)
4096
4097 def delete(test_org):
4098 gmPathLab.delete_test_org(test_org = test_org['pk_test_org'])
4099 return True
4100
4101 if msg is None:
4102 msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n')
4103
4104 return gmListWidgets.get_choices_from_list (
4105 parent = parent,
4106 msg = msg,
4107 caption = _('Showing diagnostic orgs.'),
4108 columns = [_('Name'), _('Organization'), _('Contact'), _('Comment'), '#'],
4109 single_selection = True,
4110 refresh_callback = refresh,
4111 edit_callback = edit,
4112 new_callback = edit,
4113 delete_callback = delete
4114 )
4115
4116
4117 from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl
4118
4119 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
4120
4136
4137
4138
4139
4140
4141
4142
4143
4145 has_errors = False
4146 if self._PRW_org_unit.GetData() is None:
4147 if self._PRW_org_unit.GetValue().strip() == '':
4148 has_errors = True
4149 self._PRW_org_unit.display_as_valid(valid = False)
4150 else:
4151 self._PRW_org_unit.display_as_valid(valid = True)
4152 else:
4153 self._PRW_org_unit.display_as_valid(valid = True)
4154
4155 return (not has_errors)
4156
4167
4187
4192
4197
4199 self._refresh_as_new()
4200
4203
4204
4206
4208
4209 query = """
4210 SELECT DISTINCT ON (list_label)
4211 pk_test_org AS data,
4212 unit || ' (' || organization || ')' AS field_label,
4213 unit || ' @ ' || organization AS list_label
4214 FROM clin.v_test_orgs
4215 WHERE
4216 unit %(fragment_condition)s
4217 OR
4218 organization %(fragment_condition)s
4219 ORDER BY list_label
4220 LIMIT 50"""
4221 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4222 mp.setThresholds(1, 2, 4)
4223
4224 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4225 self.matcher = mp
4226 self.SetToolTip(_('The name of the path lab/diagnostic organisation.'))
4227 self.selection_only = False
4228
4241
4244
4245
4246
4247
4264
4265
4274
4275 def delete(meta_test_type):
4276 gmPathLab.delete_meta_type(meta_type = meta_test_type['pk'])
4277 return True
4278
4279 def get_tooltip(data):
4280 if data is None:
4281 return None
4282 return data.format(with_tests = True)
4283
4284 def refresh(lctrl):
4285 mtts = gmPathLab.get_meta_test_types()
4286 items = [ [
4287 m['abbrev'],
4288 m['name'],
4289 gmTools.coalesce(m['loinc'], ''),
4290 gmTools.coalesce(m['comment'], ''),
4291 m['pk']
4292 ] for m in mtts ]
4293 lctrl.set_string_items(items)
4294 lctrl.set_data(mtts)
4295
4296
4297 msg = _(
4298 '\n'
4299 'These are the meta test types currently defined in GNUmed.\n'
4300 '\n'
4301 'Meta test types allow you to aggregate several actual test types used\n'
4302 'by pathology labs into one logical type.\n'
4303 '\n'
4304 'This is useful for grouping together results of tests which come under\n'
4305 'different names but really are the same thing. This often happens when\n'
4306 'you switch labs or the lab starts using another test method.\n'
4307 )
4308
4309 gmListWidgets.get_choices_from_list (
4310 parent = parent,
4311 msg = msg,
4312 caption = _('Showing meta test types.'),
4313 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), '#'],
4314 single_selection = True,
4315 list_tooltip_callback = get_tooltip,
4316 edit_callback = edit,
4317 new_callback = edit,
4318 delete_callback = delete,
4319 refresh_callback = refresh
4320 )
4321
4322
4367
4368
4369 from Gnumed.wxGladeWidgets import wxgMetaTestTypeEAPnl
4370
4517
4518
4519
4520
4522 ea = cTestPanelEAPnl(parent, -1)
4523 ea.data = test_panel
4524 ea.mode = gmTools.coalesce(test_panel, 'new', 'edit')
4525 dlg = gmEditArea.cGenericEditAreaDlg2 (
4526 parent = parent,
4527 id = -1,
4528 edit_area = ea,
4529 single_entry = gmTools.bool2subst((test_panel is None), False, True)
4530 )
4531 dlg.SetTitle(gmTools.coalesce(test_panel, _('Adding new test panel'), _('Editing test panel')))
4532 if dlg.ShowModal() == wx.ID_OK:
4533 dlg.DestroyLater()
4534 return True
4535 dlg.DestroyLater()
4536 return False
4537
4538
4547
4548 def delete(test_panel):
4549 gmPathLab.delete_test_panel(pk = test_panel['pk_test_panel'])
4550 return True
4551
4552 def get_tooltip(test_panel):
4553 return test_panel.format()
4554
4555 def refresh(lctrl):
4556 panels = gmPathLab.get_test_panels(order_by = 'description')
4557 items = [ [
4558 p['description'],
4559 gmTools.coalesce(p['comment'], ''),
4560 p['pk_test_panel']
4561 ] for p in panels ]
4562 lctrl.set_string_items(items)
4563 lctrl.set_data(panels)
4564
4565 gmListWidgets.get_choices_from_list (
4566 parent = parent,
4567 caption = 'GNUmed: ' + _('Test panels list'),
4568 columns = [ _('Name'), _('Comment'), '#' ],
4569 single_selection = True,
4570 refresh_callback = refresh,
4571 edit_callback = edit,
4572 new_callback = edit,
4573 delete_callback = delete,
4574 list_tooltip_callback = get_tooltip
4575 )
4576
4577
4579
4581 query = """
4582 SELECT
4583 pk_test_panel
4584 AS data,
4585 description
4586 AS field_label,
4587 description
4588 AS list_label
4589 FROM
4590 clin.v_test_panels
4591 WHERE
4592 description %(fragment_condition)s
4593 ORDER BY field_label
4594 LIMIT 30"""
4595 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4596 mp.setThresholds(1, 2, 4)
4597
4598 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4599 self.matcher = mp
4600 self.SetToolTip(_('Select a test panel.'))
4601 self.selection_only = True
4602
4607
4612
4613
4614 from Gnumed.wxGladeWidgets import wxgTestPanelEAPnl
4615
4616 -class cTestPanelEAPnl(wxgTestPanelEAPnl.wxgTestPanelEAPnl, gmEditArea.cGenericEditAreaMixin):
4617
4637
4638
4640 self._LCTRL_loincs.set_columns([_('LOINC'), _('Term'), _('Units')])
4641 self._LCTRL_loincs.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
4642
4643 self._LCTRL_loincs.delete_callback = self._remove_loincs_from_list
4644 self.__refresh_loinc_list()
4645
4646 self._PRW_loinc.final_regex = r'.*'
4647 self._PRW_loinc.add_callback_on_selection(callback = self._on_loinc_selected)
4648
4649
4651 self._LCTRL_loincs.remove_items_safely()
4652 if self.__loincs is None:
4653 if self.data is None:
4654 return
4655 self.__loincs = self.data['loincs']
4656
4657 items = []
4658 for loinc in self.__loincs:
4659 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4660 if loinc_detail is None:
4661
4662 ttypes = gmPathLab.get_measurement_types(loincs = [loinc])
4663 if len(ttypes) == 0:
4664 items.append([loinc, _('LOINC not found'), ''])
4665 else:
4666 for tt in ttypes:
4667 items.append([loinc, _('not a LOINC') + u'; %(name)s @ %(name_org)s [#%(pk_test_type)s]' % tt, ''])
4668 continue
4669 items.append ([
4670 loinc,
4671 loinc_detail['term'],
4672 gmTools.coalesce(loinc_detail['example_units'], '', '%s')
4673 ])
4674
4675 self._LCTRL_loincs.set_string_items(items)
4676 self._LCTRL_loincs.set_column_widths()
4677
4678
4679
4680
4682 validity = True
4683
4684 if self.__loincs is None:
4685 if self.data is not None:
4686 self.__loincs = self.data['loincs']
4687
4688 if self.__loincs is None:
4689
4690 self.StatusText = _('No LOINC codes selected.')
4691 self._PRW_loinc.SetFocus()
4692
4693 if self._TCTRL_description.GetValue().strip() == '':
4694 validity = False
4695 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False)
4696 self._TCTRL_description.SetFocus()
4697 else:
4698 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True)
4699
4700 return validity
4701
4702
4711
4712
4714 self.data['description'] = self._TCTRL_description.GetValue().strip()
4715 self.data['comment'] = self._TCTRL_comment.GetValue().strip()
4716 self.data.save()
4717 if self.__loincs is not None:
4718 self.data.included_loincs = self.__loincs
4719 return True
4720
4721
4723 self._TCTRL_description.SetValue('')
4724 self._TCTRL_comment.SetValue('')
4725 self._PRW_loinc.SetText('', None)
4726 self._LBL_loinc.SetLabel('')
4727 self.__loincs = None
4728 self.__refresh_loinc_list()
4729
4730 self._TCTRL_description.SetFocus()
4731
4732
4734 self._refresh_as_new()
4735
4736
4738 self._TCTRL_description.SetValue(self.data['description'])
4739 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
4740 self._PRW_loinc.SetText('', None)
4741 self._LBL_loinc.SetLabel('')
4742 self.__loincs = self.data['loincs']
4743 self.__refresh_loinc_list()
4744
4745 self._PRW_loinc.SetFocus()
4746
4747
4748
4749
4751 loinc = self._PRW_loinc.GetData()
4752 if loinc is None:
4753 self._LBL_loinc.SetLabel('')
4754 return
4755 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4756 if loinc_detail is None:
4757 loinc_str = _('no LOINC details found')
4758 else:
4759 loinc_str = '%s: %s%s' % (
4760 loinc,
4761 loinc_detail['term'],
4762 gmTools.coalesce(loinc_detail['example_units'], '', ' (%s)')
4763 )
4764 self._LBL_loinc.SetLabel(loinc_str)
4765
4766
4788
4789
4793
4794
4796 loincs2remove = self._LCTRL_loincs.selected_item_data
4797 if loincs2remove is None:
4798 return
4799 for loinc in loincs2remove:
4800 try:
4801 while True:
4802 self.__loincs.remove(loinc[0])
4803 except ValueError:
4804 pass
4805 self.__refresh_loinc_list()
4806
4807
4808
4809
4810 if __name__ == '__main__':
4811
4812 from Gnumed.pycommon import gmLog2
4813 from Gnumed.wxpython import gmPatSearchWidgets
4814
4815 gmI18N.activate_locale()
4816 gmI18N.install_domain()
4817 gmDateTime.init()
4818
4819
4827
4835
4836
4837
4838
4839
4840
4841
4842 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
4843
4844 test_test_ea_pnl()
4845
4846
4847
4848