1 """GNUmed measurement widgets."""
2
3 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
4 __license__ = "GPL"
5
6
7 import sys
8 import logging
9 import datetime as pyDT
10 import decimal
11 import os
12 import subprocess
13 import io
14 import os.path
15
16
17 import wx
18 import wx.grid
19 import wx.adv as wxh
20
21
22 if __name__ == '__main__':
23 sys.path.insert(0, '../../')
24 from Gnumed.pycommon import gmTools
25 from Gnumed.pycommon import gmNetworkTools
26 from Gnumed.pycommon import gmI18N
27 from Gnumed.pycommon import gmShellAPI
28 from Gnumed.pycommon import gmCfg
29 from Gnumed.pycommon import gmDateTime
30 from Gnumed.pycommon import gmMatchProvider
31 from Gnumed.pycommon import gmDispatcher
32 from Gnumed.pycommon import gmMimeLib
33
34 from Gnumed.business import gmPerson
35 from Gnumed.business import gmStaff
36 from Gnumed.business import gmPathLab
37 from Gnumed.business import gmPraxis
38 from Gnumed.business import gmLOINC
39 from Gnumed.business import gmForms
40 from Gnumed.business import gmPersonSearch
41 from Gnumed.business import gmOrganization
42 from Gnumed.business import gmHL7
43 from Gnumed.business import gmIncomingData
44 from Gnumed.business import gmDocuments
45
46 from Gnumed.wxpython import gmRegetMixin
47 from Gnumed.wxpython import gmPlugin
48 from Gnumed.wxpython import gmEditArea
49 from Gnumed.wxpython import gmPhraseWheel
50 from Gnumed.wxpython import gmListWidgets
51 from Gnumed.wxpython import gmGuiHelpers
52 from Gnumed.wxpython import gmAuthWidgets
53 from Gnumed.wxpython import gmOrganizationWidgets
54 from Gnumed.wxpython import gmEMRStructWidgets
55 from Gnumed.wxpython import gmCfgWidgets
56 from Gnumed.wxpython import gmDocumentWidgets
57
58
59 _log = logging.getLogger('gm.ui')
60
61
62
63
65
66 if parent is None:
67 parent = wx.GetApp().GetTopWindow()
68
69
70 paths = gmTools.gmPaths()
71 dlg = wx.FileDialog (
72 parent = parent,
73 message = _('Show HL7 file:'),
74
75 defaultDir = os.path.join(paths.home_dir, 'gnumed'),
76 wildcard = "hl7 files|*.hl7|HL7 files|*.HL7|all files|*",
77 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
78 )
79 choice = dlg.ShowModal()
80 hl7_name = dlg.GetPath()
81 dlg.Destroy()
82 if choice != wx.ID_OK:
83 return False
84
85 formatted_name = gmHL7.format_hl7_file (
86 hl7_name,
87 skip_empty_fields = True,
88 return_filename = True,
89 fix_hl7 = True
90 )
91 gmMimeLib.call_viewer_on_file(aFile = formatted_name, block = False)
92 return True
93
94
96
97 if parent is None:
98 parent = wx.GetApp().GetTopWindow()
99
100
101 paths = gmTools.gmPaths()
102 dlg = wx.FileDialog (
103 parent = parent,
104 message = _('Extract HL7 from XML file:'),
105
106 defaultDir = os.path.join(paths.home_dir, 'gnumed'),
107 wildcard = "xml files|*.xml|XML files|*.XML|all files|*",
108 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
109 )
110 choice = dlg.ShowModal()
111 xml_name = dlg.GetPath()
112 dlg.Destroy()
113 if choice != wx.ID_OK:
114 return False
115
116 target_dir = os.path.split(xml_name)[0]
117 xml_path = './/Message'
118 hl7_name = gmHL7.extract_HL7_from_XML_CDATA(xml_name, xml_path, target_dir = target_dir)
119 if hl7_name is None:
120 gmGuiHelpers.gm_show_error (
121 title = _('Extracting HL7 from XML file'),
122 error = (
123 'Cannot unwrap HL7 data from XML file\n'
124 '\n'
125 ' [%s]\n'
126 '\n'
127 '(CDATA of [%s] nodes)'
128 ) % (
129 xml_name,
130 xml_path
131 )
132 )
133 return False
134
135 gmDispatcher.send(signal = 'statustext', msg = _('Unwrapped HL7 into [%s] from [%s].') % (hl7_name, xml_name), beep = False)
136 return True
137
138
140
141 if parent is None:
142 parent = wx.GetApp().GetTopWindow()
143
144 paths = gmTools.gmPaths()
145 dlg = wx.FileDialog (
146 parent = parent,
147 message = _('Select HL7 file for staging:'),
148
149 defaultDir = os.path.join(paths.home_dir, 'gnumed'),
150 wildcard = ".hl7 files|*.hl7|.HL7 files|*.HL7|all files|*",
151 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
152 )
153 choice = dlg.ShowModal()
154 hl7_name = dlg.GetPath()
155 dlg.Destroy()
156 if choice != wx.ID_OK:
157 return False
158
159 target_dir = os.path.join(paths.home_dir, '.gnumed', 'hl7')
160 success, PID_names = gmHL7.split_hl7_file(hl7_name, target_dir = target_dir, encoding = 'utf8')
161 if not success:
162 gmGuiHelpers.gm_show_error (
163 title = _('Staging HL7 file'),
164 error = _(
165 'There was a problem with splitting the HL7 file\n'
166 '\n'
167 ' %s'
168 ) % hl7_name
169 )
170 return False
171
172 failed_files = []
173 for PID_name in PID_names:
174 if not gmHL7.stage_single_PID_hl7_file(PID_name, source = _('generic'), encoding = 'utf8'):
175 failed_files.append(PID_name)
176 if len(failed_files) > 0:
177 gmGuiHelpers.gm_show_error (
178 title = _('Staging HL7 file'),
179 error = _(
180 'There was a problem with staging the following files\n'
181 '\n'
182 ' %s'
183 ) % '\n '.join(failed_files)
184 )
185 return False
186
187 gmDispatcher.send(signal = 'statustext', msg = _('Staged HL7 from [%s].') % hl7_name, beep = False)
188 return True
189
190
212
213 def import_hl7(staged_item):
214 if staged_item is None:
215 return False
216 if 'HL7' not in staged_item['data_type']:
217 return False
218 unset_identity_on_error = False
219 if staged_item['pk_identity_disambiguated'] is None:
220 pat = gmPerson.gmCurrentPatient()
221 if pat.connected:
222 answer = gmGuiHelpers.gm_show_question (
223 title = _('Importing HL7 data'),
224 question = _(
225 'There has not been a patient explicitely associated\n'
226 'with this chunk of HL7 data. However, the data file\n'
227 'contains the following patient identification information:\n'
228 '\n'
229 ' %s\n'
230 '\n'
231 'Do you want to import the HL7 under the current patient ?\n'
232 '\n'
233 ' %s\n'
234 '\n'
235 'Selecting [NO] makes GNUmed try to find a patient matching the HL7 data.\n'
236 ) % (
237 staged_item.patient_identification,
238 pat['description_gender']
239 ),
240 cancel_button = True
241 )
242 if answer is None:
243 return False
244 if answer is True:
245 unset_identity_on_error = True
246 staged_item['pk_identity_disambiguated'] = pat.ID
247
248 success, log_name = gmHL7.process_staged_single_PID_hl7_file(staged_item)
249 if success:
250 return True
251
252 if unset_identity_on_error:
253 staged_item['pk_identity_disambiguated'] = None
254 staged_item.save()
255
256 gmGuiHelpers.gm_show_error (
257 error = _('Error processing HL7 data.'),
258 title = _('Processing staged HL7 data.')
259 )
260 return False
261
262
263 def delete(staged_item):
264 if staged_item is None:
265 return False
266 do_delete = gmGuiHelpers.gm_show_question (
267 title = _('Deleting incoming data'),
268 question = _(
269 'Do you really want to delete the incoming data ?\n'
270 '\n'
271 'Note that deletion is not reversible.'
272 )
273 )
274 if not do_delete:
275 return False
276 return gmIncomingData.delete_incoming_data(pk_incoming_data = staged_item['pk_incoming_data_unmatched'])
277
278 def refresh(lctrl):
279 incoming = gmIncomingData.get_incoming_data()
280 items = [ [
281 gmTools.coalesce(i['data_type'], ''),
282 '%s, %s (%s) %s' % (
283 gmTools.coalesce(i['lastnames'], ''),
284 gmTools.coalesce(i['firstnames'], ''),
285 gmDateTime.pydt_strftime(dt = i['dob'], format = '%Y %b %d', accuracy = gmDateTime.acc_days, none_str = _('unknown DOB')),
286 gmTools.coalesce(i['gender'], '')
287 ),
288 gmTools.coalesce(i['external_data_id'], ''),
289 i['pk_incoming_data_unmatched']
290 ] for i in incoming ]
291 lctrl.set_string_items(items)
292 lctrl.set_data(incoming)
293
294 gmListWidgets.get_choices_from_list (
295 parent = parent,
296 msg = None,
297 caption = _('Showing unmatched incoming data'),
298 columns = [ _('Type'), _('Identification'), _('Reference'), '#' ],
299 single_selection = True,
300 can_return_empty = False,
301 ignore_OK_button = True,
302 refresh_callback = refresh,
303
304
305 delete_callback = delete,
306 left_extra_button = [_('Show'), _('Show formatted HL7'), show_hl7],
307 middle_extra_button = [_('Import'), _('Import HL7 data into patient chart'), import_hl7]
308
309 )
310
311
312
313
343
344
345 -def edit_measurement(parent=None, measurement=None, single_entry=False, fields=None):
358
359
371
372 def delete(measurement):
373 gmPathLab.delete_test_result(result = measurement)
374 return True
375
376 def do_review(lctrl):
377 data = lctrl.get_selected_item_data()
378 if len(data) == 0:
379 return
380 return review_tests(parent = parent, tests = data)
381
382 def do_plot(lctrl):
383 data = lctrl.get_selected_item_data()
384 if len(data) == 0:
385 return
386 return plot_measurements(parent = parent, tests = data)
387
388 def get_tooltip(measurement):
389 return measurement.format(with_review=True, with_evaluation=True, with_ranges=True)
390
391 def refresh(lctrl):
392 results = emr.get_test_results(order_by = 'clin_when DESC, unified_abbrev, unified_name')
393 items = [ [
394 gmDateTime.pydt_strftime (
395 r['clin_when'],
396 '%Y %b %d %H:%M',
397 accuracy = gmDateTime.acc_minutes
398 ),
399 r['unified_abbrev'],
400 '%s%s%s%s' % (
401 gmTools.bool2subst (
402 boolean = (not r['reviewed'] or (not r['review_by_you'] and r['you_are_responsible'])),
403 true_return = 'u' + gmTools.u_writing_hand,
404 false_return = ''
405 ),
406 r['unified_val'],
407 gmTools.coalesce(r['val_unit'], '', ' %s'),
408 gmTools.coalesce(r['abnormality_indicator'], '', ' %s')
409 ),
410 r['unified_name'],
411
412
413
414
415
416
417
418
419
420
421
422
423 gmTools.coalesce(r['comment'], ''),
424 r['pk_test_result']
425 ] for r in results ]
426 lctrl.set_string_items(items)
427 lctrl.set_data(results)
428
429
430 msg = _('Test results (ordered reverse-chronologically)')
431
432 return gmListWidgets.get_choices_from_list (
433 parent = parent,
434 msg = msg,
435 caption = _('Showing test results.'),
436 columns = [ _('When'), _('Abbrev'), _('Value'), _('Name'), _('Comment'), '#' ],
437 single_selection = single_selection,
438 can_return_empty = False,
439 refresh_callback = refresh,
440 edit_callback = edit,
441 new_callback = edit,
442 delete_callback = delete,
443 list_tooltip_callback = get_tooltip,
444 left_extra_button = (_('Review'), _('Review current selection'), do_review, True),
445 middle_extra_button = (_('Plot'), _('Plot current selection'), do_plot, True)
446 )
447
448
466
467
498
499
501
502 option = 'form_templates.default_gnuplot_template'
503
504 dbcfg = gmCfg.cCfgSQL()
505
506
507 default_template_name = dbcfg.get2 (
508 option = option,
509 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
510 bias = 'user'
511 )
512
513
514 if default_template_name is None:
515 gmDispatcher.send('statustext', msg = _('No default Gnuplot template configured.'), beep = False)
516 default_template = configure_default_gnuplot_template(parent = parent)
517
518 if default_template is None:
519 gmGuiHelpers.gm_show_error (
520 aMessage = _('There is no default Gnuplot one-type script template configured.'),
521 aTitle = _('Plotting test results')
522 )
523 return None
524 return default_template
525
526
527
528 try:
529 name, ver = default_template_name.split(' - ')
530 except:
531
532 _log.exception('problem splitting Gnuplot script template name [%s]', default_template_name)
533 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading Gnuplot script template.'), beep = True)
534 return None
535
536 default_template = gmForms.get_form_template(name_long = name, external_version = ver)
537 if default_template is None:
538 default_template = configure_default_gnuplot_template(parent = parent)
539
540 if default_template is None:
541 gmGuiHelpers.gm_show_error (
542 aMessage = _('Cannot load default Gnuplot script template [%s - %s]') % (name, ver),
543 aTitle = _('Plotting test results')
544 )
545 return None
546
547 return default_template
548
549
550 -def plot_measurements(parent=None, tests=None, format=None, show_year = True, use_default_template=False):
576
577
578 -def plot_adjacent_measurements(parent=None, test=None, format=None, show_year=True, plot_singular_result=True, use_default_template=False):
579
580 earlier, later = test.get_adjacent_results(desired_earlier_results = 2, desired_later_results = 2)
581 results2plot = []
582 if earlier is not None:
583 results2plot.extend(earlier)
584 results2plot.append(test)
585 if later is not None:
586 results2plot.extend(later)
587 if len(results2plot) == 1:
588 if not plot_singular_result:
589 return
590 plot_measurements (
591 parent = parent,
592 tests = results2plot,
593 format = format,
594 show_year = show_year,
595 use_default_template = use_default_template
596 )
597
598
599
600
601
602
603
604
605
606
607
608 from Gnumed.wxGladeWidgets import wxgMeasurementsAsListPnl
609
610 -class cMeasurementsAsListPnl(wxgMeasurementsAsListPnl.wxgMeasurementsAsListPnl, gmRegetMixin.cRegetOnPaintMixin):
611 """A class for displaying all measurement results as a simple list.
612
613 - operates on a cPatient instance handed to it and NOT on the currently active patient
614 """
624
625
626
627
629 self._LCTRL_results.set_columns([_('When'), _('Test'), _('Result'), _('Reference')])
630 self._LCTRL_results.edit_callback = self._on_edit
631
632
635
636
638 if self.__patient is None:
639 self._TCTRL_measurements.SetValue('')
640 return
641
642 results = self.__patient.emr.get_test_results(order_by = 'clin_when DESC, unified_abbrev, unified_name')
643 items = []
644 data = []
645 for r in results:
646 range_info = gmTools.coalesce (
647 r.formatted_clinical_range,
648 r.formatted_normal_range
649 )
650 review = gmTools.bool2subst (
651 r['reviewed'],
652 '',
653 ' ' + gmTools.u_writing_hand,
654 ' ' + gmTools.u_writing_hand
655 )
656 items.append ([
657 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes),
658 r['abbrev_tt'],
659 '%s%s%s%s' % (
660 gmTools.strip_empty_lines(text = r['unified_val'])[0],
661 gmTools.coalesce(r['val_unit'], '', ' %s'),
662 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
663 review
664 ),
665 gmTools.coalesce(range_info, '')
666 ])
667 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
668
669 self._LCTRL_results.set_string_items(items)
670 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
671 self._LCTRL_results.set_data(data)
672 if len(items) > 0:
673 self._LCTRL_results.Select(idx = 0, on = 1)
674 self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted'])
675
676 self._LCTRL_results.SetFocus()
677
678
685
686
687
688
690 if self.__patient is None:
691 return True
692
693 if kwds['pk_identity'] is not None:
694 if kwds['pk_identity'] != self.__patient.ID:
695 return True
696
697 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
698 return True
699
700 self._schedule_data_reget()
701 return True
702
703
708
709
710
711
713 self.__repopulate_ui()
714 return True
715
716
717
718
720 return self.__patient
721
723 if (self.__patient is None) and (patient is None):
724 return
725 if (self.__patient is None) or (patient is None):
726 self.__patient = patient
727 self._schedule_data_reget()
728 return
729 if self.__patient.ID == patient.ID:
730 return
731 self.__patient = patient
732 self._schedule_data_reget()
733
734 patient = property(_get_patient, _set_patient)
735
736
737 from Gnumed.wxGladeWidgets import wxgMeasurementsByDayPnl
738
739 -class cMeasurementsByDayPnl(wxgMeasurementsByDayPnl.wxgMeasurementsByDayPnl, gmRegetMixin.cRegetOnPaintMixin):
740 """A class for displaying measurement results as a list partitioned by day.
741
742 - operates on a cPatient instance handed to it and NOT on the currently active patient
743 """
755
756
757
758
760 self._LCTRL_days.set_columns([_('Day')])
761 self._LCTRL_results.set_columns([_('Time'), _('Test'), _('Result'), _('Reference')])
762 self._LCTRL_results.edit_callback = self._on_edit
763 self._LBL_no_of_docs.SetLabel(_('no related documents found'))
764 dbcfg = gmCfg.cCfgSQL()
765 lab_doc_types = dbcfg.get2 (
766 option = 'horstspace.lab_doc_types',
767 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
768 bias = 'user'
769 )
770 if lab_doc_types is None:
771 txt = _('No document types declared to contain lab results.')
772 elif len(lab_doc_types) == 0:
773 txt = _('No document types declared to contain lab results.')
774 else:
775 txt = _('Document types declared to contain lab results:')
776 txt += '\n '
777 txt += '\n '.join(lab_doc_types)
778 self._LBL_no_of_docs.SetToolTip(txt)
779
780
783
784
789
790
811
812
819
820
821
822
824 if self.__patient is None:
825 return True
826
827 if kwds['pk_identity'] is not None:
828 if kwds['pk_identity'] != self.__patient.ID:
829 return True
830
831 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
832 return True
833
834 self._schedule_data_reget()
835 return True
836
837
839 event.Skip()
840
841 day = self._LCTRL_days.get_item_data(item_idx = event.Index)['clin_when_day']
842 results = self.__patient.emr.get_results_for_day(timestamp = day)
843 items = []
844 data = []
845 for r in results:
846 range_info = gmTools.coalesce (
847 r.formatted_clinical_range,
848 r.formatted_normal_range
849 )
850 review = gmTools.bool2subst (
851 r['reviewed'],
852 '',
853 ' ' + gmTools.u_writing_hand,
854 ' ' + gmTools.u_writing_hand
855 )
856 items.append ([
857 gmDateTime.pydt_strftime(r['clin_when'], '%H:%M'),
858 r['abbrev_tt'],
859 '%s%s%s%s' % (
860 gmTools.strip_empty_lines(text = r['unified_val'])[0],
861 gmTools.coalesce(r['val_unit'], '', ' %s'),
862 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
863 review
864 ),
865 gmTools.coalesce(range_info, '')
866 ])
867 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
868
869 self._LCTRL_results.set_string_items(items)
870 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
871 self._LCTRL_results.set_data(data)
872 self._LCTRL_results.Select(idx = 0, on = 1)
873
874
876 event.Skip()
877 item_data = self._LCTRL_results.get_item_data(item_idx = event.Index)
878 self._TCTRL_measurements.SetValue(item_data['formatted'])
879 pk_episode = item_data['data']['pk_episode']
880 if pk_episode == self.__pk_curr_episode:
881 return
882 self.__pk_curr_episode = pk_episode
883 self._LBL_no_of_docs.SetLabel(_('no related documents found'))
884 self._BTN_list_docs.Disable()
885 dbcfg = gmCfg.cCfgSQL()
886 lab_doc_types = dbcfg.get2 (
887 option = 'horstspace.lab_doc_types',
888 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
889 bias = 'user'
890 )
891 if lab_doc_types is None:
892 return
893 d_types = gmDocuments.map_types2pk(lab_doc_types)
894 if len(d_types) is None:
895 return
896 docs = gmDocuments.search_for_documents (
897 pk_episode = pk_episode,
898 pk_types = [ dt['pk_doc_type'] for dt in d_types ]
899 )
900 if len(docs) == 0:
901 return
902 self._LBL_no_of_docs.SetLabel(_("Related documents: %s") % len(docs))
903 self._LBL_no_of_docs.Refresh()
904 self._BTN_list_docs.Enable()
905
906
926
927
929 event.Skip()
930 doc_types = gmDocuments.get_document_types()
931 gmCfgWidgets.configure_list_from_list_option (
932 parent = self,
933 message = _('Select the document types which are expected to contain lab results.'),
934 option = 'horstspace.lab_doc_types',
935 bias = 'user',
936 choices = [ dt['l10n_type'] for dt in doc_types ],
937 columns = [_('Document types')]
938
939
940
941 )
942 dbcfg = gmCfg.cCfgSQL()
943 lab_doc_types = dbcfg.get2 (
944 option = 'horstspace.lab_doc_types',
945 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
946 bias = 'user'
947 )
948 if lab_doc_types is None:
949 txt = _('No document types declared to contain lab results.')
950 elif len(lab_doc_types) == 0:
951 txt = _('No document types declared to contain lab results.')
952 else:
953 txt = _('Document types declared to contain lab results:')
954 txt += '\n '
955 txt += '\n '.join(lab_doc_types)
956 self._LBL_no_of_docs.SetToolTip(txt)
957
958
959
960
962 self.__repopulate_ui()
963 return True
964
965
966
967
969 return self.__patient
970
972 if (self.__patient is None) and (patient is None):
973 return
974 if patient is None:
975 self.__patient = None
976 self.__clear()
977 return
978 if self.__patient is None:
979 self.__patient = patient
980 self._schedule_data_reget()
981 return
982 if self.__patient.ID == patient.ID:
983 return
984 self.__patient = patient
985 self._schedule_data_reget()
986
987 patient = property(_get_patient, _set_patient)
988
989
990 from Gnumed.wxGladeWidgets import wxgMeasurementsByIssuePnl
991
992 -class cMeasurementsByIssuePnl(wxgMeasurementsByIssuePnl.wxgMeasurementsByIssuePnl, gmRegetMixin.cRegetOnPaintMixin):
993 """A class for displaying measurement results as a list partitioned by issue/episode.
994
995 - operates on a cPatient instance handed to it and NOT on the currently active patient
996 """
1006
1007
1008
1009
1016
1017
1020
1021
1026
1027
1043
1044
1051
1052
1053
1054
1056 if self.__patient is None:
1057 return True
1058
1059 if kwds['pk_identity'] is not None:
1060 if kwds['pk_identity'] != self.__patient.ID:
1061 return True
1062
1063 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1064 return True
1065
1066 self._schedule_data_reget()
1067 return True
1068
1069
1071 event.Skip()
1072
1073 pk_issue = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_issue']
1074 if pk_issue is None:
1075 pk_episode = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_episode']
1076 results = self.__patient.emr.get_results_for_episode(pk_episode = pk_episode)
1077 else:
1078 results = self.__patient.emr.get_results_for_issue(pk_health_issue = pk_issue)
1079 items = []
1080 data = []
1081 for r in results:
1082 range_info = gmTools.coalesce (
1083 r.formatted_clinical_range,
1084 r.formatted_normal_range
1085 )
1086 review = gmTools.bool2subst (
1087 r['reviewed'],
1088 '',
1089 ' ' + gmTools.u_writing_hand,
1090 ' ' + gmTools.u_writing_hand
1091 )
1092 items.append ([
1093 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M'),
1094 r['abbrev_tt'],
1095 '%s%s%s%s' % (
1096 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1097 gmTools.coalesce(r['val_unit'], '', ' %s'),
1098 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1099 review
1100 ),
1101 gmTools.coalesce(range_info, '')
1102 ])
1103 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
1104
1105 self._LCTRL_results.set_string_items(items)
1106 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1107 self._LCTRL_results.set_data(data)
1108 self._LCTRL_results.Select(idx = 0, on = 1)
1109 self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted'])
1110
1111
1116
1117
1118
1119
1121 self.__repopulate_ui()
1122 return True
1123
1124
1125
1126
1128 return self.__patient
1129
1131 if (self.__patient is None) and (patient is None):
1132 return
1133 if patient is None:
1134 self.__patient = None
1135 self.__clear()
1136 return
1137 if self.__patient is None:
1138 self.__patient = patient
1139 self._schedule_data_reget()
1140 return
1141 if self.__patient.ID == patient.ID:
1142 return
1143 self.__patient = patient
1144 self._schedule_data_reget()
1145
1146 patient = property(_get_patient, _set_patient)
1147
1148
1149 from Gnumed.wxGladeWidgets import wxgMeasurementsByBatteryPnl
1150
1152 """A grid class for displaying measurement results filtered by battery/panel.
1153
1154 - operates on a cPatient instance handed to it and NOT on the currently active patient
1155 """
1165
1166
1167
1168
1171
1172
1178
1179
1181 self._GRID_results_battery.patient = self.__patient
1182 return True
1183
1184
1186 if panel is None:
1187 self._TCTRL_panel_comment.SetValue('')
1188 self._GRID_results_battery.panel_to_show = None
1189 else:
1190 pnl = self._PRW_panel.GetData(as_instance = True)
1191 self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
1192 pnl['comment'],
1193 ''
1194 ))
1195 self._GRID_results_battery.panel_to_show = pnl
1196
1197
1198
1200 self._TCTRL_panel_comment.SetValue('')
1201 if self._PRW_panel.GetValue().strip() == '':
1202 self._GRID_results_battery.panel_to_show = None
1203
1204
1205
1206
1207
1209 if self.__patient is None:
1210 return True
1211
1212 if kwds['pk_identity'] is not None:
1213 if kwds['pk_identity'] != self.__patient.ID:
1214 return True
1215
1216 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1217 return True
1218
1219 self._schedule_data_reget()
1220 return True
1221
1222
1225
1226
1228 wx.CallAfter(self.__on_panel_selected, panel=panel)
1229
1230
1232 wx.CallAfter(self.__on_panel_selection_modified)
1233
1234
1235
1236
1238 self.__repopulate_ui()
1239 return True
1240
1241
1242
1243
1245 return self.__patient
1246
1248 if (self.__patient is None) and (patient is None):
1249 return
1250 if (self.__patient is None) or (patient is None):
1251 self.__patient = patient
1252 self._schedule_data_reget()
1253 return
1254 if self.__patient.ID == patient.ID:
1255 return
1256 self.__patient = patient
1257 self._schedule_data_reget()
1258
1259 patient = property(_get_patient, _set_patient)
1260
1261
1262 from Gnumed.wxGladeWidgets import wxgMeasurementsAsTablePnl
1263
1264 -class cMeasurementsAsTablePnl(wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl, gmRegetMixin.cRegetOnPaintMixin):
1265 """A panel for holding a grid displaying all measurement results.
1266
1267 - operates on a cPatient instance handed to it and NOT on the currently active patient
1268 """
1278
1279
1280
1281
1283 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
1284
1285 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
1286 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
1287
1288 item = self.__action_button_popup.Append(-1, _('Plot'))
1289 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299 item = self.__action_button_popup.Append(-1, _('&Delete'))
1300 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
1301
1302
1303
1304
1305 self._GRID_results_all.show_by_panel = False
1306
1307
1310
1311
1313 self._GRID_results_all.patient = self.__patient
1314
1315 self.Layout()
1316 return True
1317
1318
1321
1322
1325
1326
1329
1330
1331
1332
1334 if self.__patient is None:
1335 return True
1336
1337 if kwds['pk_identity'] is not None:
1338 if kwds['pk_identity'] != self.__patient.ID:
1339 return True
1340
1341 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1342 return True
1343
1344 self._schedule_data_reget()
1345 return True
1346
1347
1350
1351
1355
1356
1359
1360
1366
1367
1368
1369
1371 self.__repopulate_ui()
1372 return True
1373
1374
1375
1376
1378 return self.__patient
1379
1381 if (self.__patient is None) and (patient is None):
1382 return
1383 if (self.__patient is None) or (patient is None):
1384 self.__patient = patient
1385 self._schedule_data_reget()
1386 return
1387 if self.__patient.ID == patient.ID:
1388 return
1389 self.__patient = patient
1390 self._schedule_data_reget()
1391
1392 patient = property(_get_patient, _set_patient)
1393
1394
1395
1396
1398 """Notebook displaying measurements pages:
1399
1400 - by test battery
1401 - by day
1402 - by issue/episode
1403 - full grid
1404 - full list
1405
1406 Used as a main notebook plugin page.
1407
1408 Operates on the active patient.
1409 """
1410
1425
1426
1427
1428
1430 for page_idx in range(self.GetPageCount()):
1431 page = self.GetPage(page_idx)
1432 page.patient = None
1433
1434
1435 - def _post_patient_selection(self, **kwds):
1436 for page_idx in range(self.GetPageCount()):
1437 page = self.GetPage(page_idx)
1438 page.patient = self.__patient.patient
1439
1440
1441
1442
1444 if self.__patient.connected:
1445 pat = self.__patient.patient
1446 else:
1447 pat = None
1448 for page_idx in range(self.GetPageCount()):
1449 page = self.GetPage(page_idx)
1450 page.patient = pat
1451
1452 return True
1453
1454
1455
1456
1458
1459
1460 new_page = cMeasurementsByDayPnl(self, -1)
1461 new_page.patient = None
1462 self.AddPage (
1463 page = new_page,
1464 text = _('Days'),
1465 select = True
1466 )
1467
1468
1469 new_page = cMeasurementsByIssuePnl(self, -1)
1470 new_page.patient = None
1471 self.AddPage (
1472 page = new_page,
1473 text = _('Problems'),
1474 select = False
1475 )
1476
1477
1478 new_page = cMeasurementsByBatteryPnl(self, -1)
1479 new_page.patient = None
1480 self.AddPage (
1481 page = new_page,
1482 text = _('Panels'),
1483 select = False
1484 )
1485
1486
1487 new_page = cMeasurementsAsTablePnl(self, -1)
1488 new_page.patient = None
1489 self.AddPage (
1490 page = new_page,
1491 text = _('Table'),
1492 select = False
1493 )
1494
1495
1496 new_page = cMeasurementsAsListPnl(self, -1)
1497 new_page.patient = None
1498 self.AddPage (
1499 page = new_page,
1500 text = _('List'),
1501 select = False
1502 )
1503
1504
1505
1506
1508 return self.__patient
1509
1511 self.__patient = patient
1512 if self.__patient.connected:
1513 pat = self.__patient.patient
1514 else:
1515 pat = None
1516 for page_idx in range(self.GetPageCount()):
1517 page = self.GetPage(page_idx)
1518 page.patient = pat
1519
1520 patient = property(_get_patient, _set_patient)
1521
1522
1524 """A grid class for displaying measurement results.
1525
1526 - operates on a cPatient instance handed to it
1527 - does NOT listen to the currently active patient
1528 - thereby it can display any patient at any time
1529 """
1530
1531
1532
1533
1534
1536
1537 wx.grid.Grid.__init__(self, *args, **kwargs)
1538
1539 self.__patient = None
1540 self.__panel_to_show = None
1541 self.__show_by_panel = False
1542 self.__cell_data = {}
1543 self.__row_label_data = []
1544 self.__col_label_data = []
1545
1546 self.__prev_row = None
1547 self.__prev_col = None
1548 self.__prev_label_row = None
1549 self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::'))
1550
1551 self.__init_ui()
1552 self.__register_events()
1553
1554
1555
1556
1558 if not self.IsSelection():
1559 gmDispatcher.send(signal = 'statustext', msg = _('No results selected for deletion.'))
1560 return True
1561
1562 selected_cells = self.get_selected_cells()
1563 if len(selected_cells) > 20:
1564 results = None
1565 msg = _(
1566 'There are %s results marked for deletion.\n'
1567 '\n'
1568 'Are you sure you want to delete these results ?'
1569 ) % len(selected_cells)
1570 else:
1571 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1572 txt = '\n'.join([ '%s %s (%s): %s %s%s' % (
1573 r['clin_when'].strftime('%x %H:%M'),
1574 r['unified_abbrev'],
1575 r['unified_name'],
1576 r['unified_val'],
1577 r['val_unit'],
1578 gmTools.coalesce(r['abnormality_indicator'], '', ' (%s)')
1579 ) for r in results
1580 ])
1581 msg = _(
1582 'The following results are marked for deletion:\n'
1583 '\n'
1584 '%s\n'
1585 '\n'
1586 'Are you sure you want to delete these results ?'
1587 ) % txt
1588
1589 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
1590 self,
1591 -1,
1592 caption = _('Deleting test results'),
1593 question = msg,
1594 button_defs = [
1595 {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False},
1596 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True}
1597 ]
1598 )
1599 decision = dlg.ShowModal()
1600
1601 if decision == wx.ID_YES:
1602 if results is None:
1603 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1604 for result in results:
1605 gmPathLab.delete_test_result(result)
1606
1607
1609 if not self.IsSelection():
1610 gmDispatcher.send(signal = 'statustext', msg = _('Cannot sign results. No results selected.'))
1611 return True
1612
1613 selected_cells = self.get_selected_cells()
1614 tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1615
1616 return review_tests(parent = self, tests = tests)
1617
1618
1620
1621 if not self.IsSelection():
1622 gmDispatcher.send(signal = 'statustext', msg = _('Cannot plot results. No results selected.'))
1623 return True
1624
1625 tests = self.__cells_to_data (
1626 cells = self.get_selected_cells(),
1627 exclude_multi_cells = False,
1628 auto_include_multi_cells = True
1629 )
1630
1631 plot_measurements(parent = self, tests = tests)
1632
1634
1635 sel_block_top_left = self.GetSelectionBlockTopLeft()
1636 sel_block_bottom_right = self.GetSelectionBlockBottomRight()
1637 sel_cols = self.GetSelectedCols()
1638 sel_rows = self.GetSelectedRows()
1639
1640 selected_cells = []
1641
1642
1643 selected_cells += self.GetSelectedCells()
1644
1645
1646 selected_cells += list (
1647 (row, col)
1648 for row in sel_rows
1649 for col in range(self.GetNumberCols())
1650 )
1651
1652
1653 selected_cells += list (
1654 (row, col)
1655 for row in range(self.GetNumberRows())
1656 for col in sel_cols
1657 )
1658
1659
1660 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()):
1661 selected_cells += [
1662 (row, col)
1663 for row in range(top_left[0], bottom_right[0] + 1)
1664 for col in range(top_left[1], bottom_right[1] + 1)
1665 ]
1666
1667 return set(selected_cells)
1668
1669 - def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
1670 """Select a range of cells according to criteria.
1671
1672 unsigned_only: include only those which are not signed at all yet
1673 accountable_only: include only those for which the current user is responsible
1674 keep_preselections: broaden (rather than replace) the range of selected cells
1675
1676 Combinations are powerful !
1677 """
1678 wx.BeginBusyCursor()
1679 self.BeginBatch()
1680
1681 if not keep_preselections:
1682 self.ClearSelection()
1683
1684 for col_idx in self.__cell_data.keys():
1685 for row_idx in self.__cell_data[col_idx].keys():
1686
1687
1688 do_not_include = False
1689 for result in self.__cell_data[col_idx][row_idx]:
1690 if unsigned_only:
1691 if result['reviewed']:
1692 do_not_include = True
1693 break
1694 if accountables_only:
1695 if not result['you_are_responsible']:
1696 do_not_include = True
1697 break
1698 if do_not_include:
1699 continue
1700
1701 self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True)
1702
1703 self.EndBatch()
1704 wx.EndBusyCursor()
1705
1706
1708 self.empty_grid()
1709 if self.__patient is None:
1710 return
1711
1712 if self.__show_by_panel:
1713 if self.__panel_to_show is None:
1714 return
1715 tests = self.__panel_to_show.get_test_types_for_results (
1716 self.__patient.ID,
1717 order_by = 'unified_abbrev',
1718 unique_meta_types = True
1719 )
1720 self.__repopulate_grid (
1721 tests4rows = tests,
1722 test_pks2show = [ tt['pk_test_type'] for tt in self.__panel_to_show['test_types'] ]
1723 )
1724 return
1725
1726 emr = self.__patient.emr
1727 tests = emr.get_test_types_for_results(order_by = 'unified_abbrev', unique_meta_types = True)
1728 self.__repopulate_grid(tests4rows = tests)
1729
1730
1732
1733 if len(tests4rows) == 0:
1734 return
1735
1736 emr = self.__patient.emr
1737
1738 self.__row_label_data = tests4rows
1739 row_labels = [ '%s%s' % (
1740 gmTools.bool2subst(test_type['is_fake_meta_type'], '', gmTools.u_sum, ''),
1741 test_type['unified_abbrev']
1742 ) for test_type in self.__row_label_data
1743 ]
1744
1745 self.__col_label_data = [ d['clin_when_day'] for d in emr.get_dates_for_results (
1746 tests = test_pks2show,
1747 reverse_chronological = True
1748 )]
1749 col_labels = [ gmDateTime.pydt_strftime(date, self.__date_format, accuracy = gmDateTime.acc_days) for date in self.__col_label_data ]
1750
1751 results = emr.get_test_results_by_date (
1752 tests = test_pks2show,
1753 reverse_chronological = True
1754 )
1755
1756 self.BeginBatch()
1757
1758
1759 self.AppendRows(numRows = len(row_labels))
1760 for row_idx in range(len(row_labels)):
1761 self.SetRowLabelValue(row_idx, row_labels[row_idx])
1762
1763
1764 self.AppendCols(numCols = len(col_labels))
1765 for col_idx in range(len(col_labels)):
1766 self.SetColLabelValue(col_idx, col_labels[col_idx])
1767
1768
1769 for result in results:
1770 row_idx = row_labels.index('%s%s' % (
1771 gmTools.bool2subst(result['is_fake_meta_type'], '', gmTools.u_sum, ''),
1772 result['unified_abbrev']
1773 ))
1774 col_idx = col_labels.index(gmDateTime.pydt_strftime(result['clin_when'], self.__date_format, accuracy = gmDateTime.acc_days))
1775
1776 try:
1777 self.__cell_data[col_idx]
1778 except KeyError:
1779 self.__cell_data[col_idx] = {}
1780
1781
1782 if row_idx in self.__cell_data[col_idx]:
1783 self.__cell_data[col_idx][row_idx].append(result)
1784 self.__cell_data[col_idx][row_idx].sort(key = lambda x: x['clin_when'], reverse = True)
1785 else:
1786 self.__cell_data[col_idx][row_idx] = [result]
1787
1788
1789 vals2display = []
1790 cell_has_out_of_bounds_value = False
1791 for sub_result in self.__cell_data[col_idx][row_idx]:
1792
1793 if sub_result.is_considered_abnormal:
1794 cell_has_out_of_bounds_value = True
1795
1796 abnormality_indicator = sub_result.formatted_abnormality_indicator
1797 if abnormality_indicator is None:
1798 abnormality_indicator = ''
1799 if abnormality_indicator != '':
1800 abnormality_indicator = ' (%s)' % abnormality_indicator[:3]
1801
1802 missing_review = False
1803
1804
1805 if not sub_result['reviewed']:
1806 missing_review = True
1807
1808 else:
1809
1810 if sub_result['you_are_responsible'] and not sub_result['review_by_you']:
1811 missing_review = True
1812
1813 needs_superscript = False
1814
1815
1816 if sub_result.is_long_text:
1817 lines = gmTools.strip_empty_lines (
1818 text = sub_result['unified_val'],
1819 eol = '\n',
1820 return_list = True
1821 )
1822 needs_superscript = True
1823 tmp = lines[0][:7]
1824 else:
1825 val = gmTools.strip_empty_lines (
1826 text = sub_result['unified_val'],
1827 eol = '\n',
1828 return_list = False
1829 ).replace('\n', '//')
1830 if len(val) > 8:
1831 needs_superscript = True
1832 tmp = val[:7]
1833 else:
1834 tmp = '%.8s' % val[:8]
1835
1836
1837 tmp = '%s%.6s' % (tmp, abnormality_indicator)
1838
1839
1840 has_sub_result_comment = gmTools.coalesce (
1841 gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']),
1842 ''
1843 ).strip() != ''
1844 if has_sub_result_comment:
1845 needs_superscript = True
1846
1847 if needs_superscript:
1848 tmp = '%s%s' % (tmp, gmTools.u_superscript_one)
1849
1850
1851 if missing_review:
1852 tmp = '%s %s' % (tmp, gmTools.u_writing_hand)
1853 else:
1854 if sub_result['is_clinically_relevant']:
1855 tmp += ' !'
1856
1857
1858 if len(self.__cell_data[col_idx][row_idx]) > 1:
1859 tmp = '%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp)
1860
1861 vals2display.append(tmp)
1862
1863 self.SetCellValue(row_idx, col_idx, '\n'.join(vals2display))
1864 self.SetCellAlignment(row_idx, col_idx, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875 if cell_has_out_of_bounds_value:
1876
1877 self.SetCellBackgroundColour(row_idx, col_idx, 'PALE TURQUOISE')
1878
1879 self.EndBatch()
1880
1881 self.AutoSize()
1882 self.AdjustScrollbars()
1883 self.ForceRefresh()
1884
1885
1886
1887 return
1888
1889
1891 self.BeginBatch()
1892 self.ClearGrid()
1893
1894
1895 if self.GetNumberRows() > 0:
1896 self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
1897 if self.GetNumberCols() > 0:
1898 self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
1899 self.EndBatch()
1900 self.__cell_data = {}
1901 self.__row_label_data = []
1902 self.__col_label_data = []
1903
1904
1922
1923
1949
1950
1951
1952
1954
1955 self.SetMinSize((10, 10))
1956
1957 self.CreateGrid(0, 1)
1958 self.EnableEditing(0)
1959 self.EnableDragGridSize(1)
1960
1961
1962
1963
1964
1965
1966 self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE)
1967
1968 self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE)
1969 font = self.GetLabelFont()
1970 font.SetWeight(wx.FONTWEIGHT_LIGHT)
1971 self.SetLabelFont(font)
1972
1973
1974 dbcfg = gmCfg.cCfgSQL()
1975 url = dbcfg.get2 (
1976 option = 'external.urls.measurements_encyclopedia',
1977 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
1978 bias = 'user',
1979 default = gmPathLab.URL_test_result_information
1980 )
1981
1982 self.__WIN_corner = self.GetGridCornerLabelWindow()
1983
1984 LNK_lab = wxh.HyperlinkCtrl (
1985 self.__WIN_corner,
1986 -1,
1987 label = _('Tests'),
1988 style = wxh.HL_DEFAULT_STYLE
1989 )
1990 LNK_lab.SetURL(url)
1991 LNK_lab.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND))
1992 LNK_lab.SetToolTip(_(
1993 'Navigate to an encyclopedia of measurements\n'
1994 'and test methods on the web.\n'
1995 '\n'
1996 ' <%s>'
1997 ) % url)
1998
1999 SZR_inner = wx.BoxSizer(wx.HORIZONTAL)
2000 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)
2001 SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0)
2002 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0)
2003
2004 SZR_corner = wx.BoxSizer(wx.VERTICAL)
2005 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)
2006 SZR_corner.Add(SZR_inner, 0, wx.EXPAND)
2007 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0)
2008
2009 self.__WIN_corner.SetSizer(SZR_corner)
2010 SZR_corner.Fit(self.__WIN_corner)
2011
2012
2014 self.__WIN_corner.Layout()
2015
2016
2017 - def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
2018 """List of <cells> must be in row / col order."""
2019 data = []
2020 for row, col in cells:
2021 try:
2022
2023 data_list = self.__cell_data[col][row]
2024 except KeyError:
2025 continue
2026
2027 if len(data_list) == 1:
2028 data.append(data_list[0])
2029 continue
2030
2031 if exclude_multi_cells:
2032 gmDispatcher.send(signal = 'statustext', msg = _('Excluding multi-result field from further processing.'))
2033 continue
2034
2035 if auto_include_multi_cells:
2036 data.extend(data_list)
2037 continue
2038
2039 data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list)
2040 if data_to_include is None:
2041 continue
2042 data.extend(data_to_include)
2043
2044 return data
2045
2046
2048 data = gmListWidgets.get_choices_from_list (
2049 parent = self,
2050 msg = _(
2051 'Your selection includes a field with multiple results.\n'
2052 '\n'
2053 'Please select the individual results you want to work on:'
2054 ),
2055 caption = _('Selecting test results'),
2056 choices = [ [d['clin_when'], '%s: %s' % (d['abbrev_tt'], d['name_tt']), d['unified_val']] for d in cell_data ],
2057 columns = [ _('Date / Time'), _('Test'), _('Result') ],
2058 data = cell_data,
2059 single_selection = single_selection
2060 )
2061 return data
2062
2063
2064
2065
2067
2068 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
2069 self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels)
2070
2071
2072
2073 self.Bind(wx.EVT_SIZE, self.__resize_corner_window)
2074
2075
2076 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
2077
2078
2080 col = evt.GetCol()
2081 row = evt.GetRow()
2082
2083 try:
2084 self.__cell_data[col][row]
2085 except KeyError:
2086 fields = {}
2087 col_date = self.__col_label_data[col]
2088 fields['clin_when'] = {'data': col_date}
2089 test_type = self.__row_label_data[row]
2090 temporally_closest_result_of_row_type = test_type.meta_test_type.get_temporally_closest_result(col_date, self.__patient.ID)
2091 if temporally_closest_result_of_row_type is not None:
2092 fields['pk_test_type'] = {'data': temporally_closest_result_of_row_type['pk_test_type']}
2093 same_day_results = gmPathLab.get_results_for_day (
2094 timestamp = col_date,
2095 patient = self.__patient.ID,
2096 order_by = None
2097 )
2098 if len(same_day_results) > 0:
2099 fields['pk_episode'] = {'data': same_day_results[0]['pk_episode']}
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109 edit_measurement (
2110 parent = self,
2111 measurement = None,
2112 single_entry = True,
2113 fields = fields
2114 )
2115 return
2116
2117 if len(self.__cell_data[col][row]) > 1:
2118 data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True)
2119 else:
2120 data = self.__cell_data[col][row][0]
2121
2122 if data is None:
2123 return
2124
2125 edit_measurement(parent = self, measurement = data, single_entry = True)
2126
2127
2128
2129
2130
2131
2132
2133
2135
2136
2137
2138 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2139
2140 row = self.YToRow(y)
2141
2142 if self.__prev_label_row == row:
2143 return
2144
2145 self.__prev_label_row == row
2146
2147 evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row))
2148
2149
2150
2151
2152
2153
2154
2155
2157 """Calculate where the mouse is and set the tooltip dynamically."""
2158
2159
2160
2161 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175 row, col = self.XYToCell(x, y)
2176
2177 if (row == self.__prev_row) and (col == self.__prev_col):
2178 return
2179
2180 self.__prev_row = row
2181 self.__prev_col = col
2182
2183 evt.GetEventObject().SetToolTip(self.get_cell_tooltip(col=col, row=row))
2184
2185
2186
2187
2189 return self.__patient
2190
2194
2195 patient = property(_get_patient, _set_patient)
2196
2200
2201 panel_to_show = property(lambda x:x, _set_panel_to_show)
2202
2206
2207 show_by_panel = property(lambda x:x, _set_show_by_panel)
2208
2209
2210
2211
2212 from Gnumed.wxGladeWidgets import wxgMeasurementsPnl
2213
2214 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
2215 """Panel holding a grid with lab data. Used as notebook page."""
2216
2224
2225
2226
2228 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
2229 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
2230 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._schedule_data_reget)
2231 gmDispatcher.connect(signal = 'clin.reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
2232
2234 self._schedule_data_reget()
2235
2237 self._GRID_results_all.patient = None
2238 self._GRID_results_battery.patient = None
2239
2242
2246
2250
2253
2259
2262
2282
2285
2288
2291
2293 wx.CallAfter(self.__on_panel_selected, panel=panel)
2294
2296 if panel is None:
2297 self._TCTRL_panel_comment.SetValue('')
2298 self._GRID_results_battery.panel_to_show = None
2299
2300 self._PNL_results_battery_grid.Hide()
2301 else:
2302 pnl = self._PRW_panel.GetData(as_instance = True)
2303 self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
2304 pnl['comment'],
2305 ''
2306 ))
2307 self._GRID_results_battery.panel_to_show = pnl
2308
2309 self._PNL_results_battery_grid.Show()
2310 self._GRID_results_battery.Fit()
2311 self._GRID_results_all.Fit()
2312 self.Layout()
2313
2315 wx.CallAfter(self.__on_panel_selection_modified)
2316
2318 self._TCTRL_panel_comment.SetValue('')
2319 if self._PRW_panel.GetValue().strip() == '':
2320 self._GRID_results_battery.panel_to_show = None
2321
2322 self._PNL_results_battery_grid.Hide()
2323 self.Layout()
2324
2325
2326
2328 self.SetMinSize((10, 10))
2329
2330 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
2331
2332 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
2333 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
2334
2335 item = self.__action_button_popup.Append(-1, _('Plot'))
2336 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
2337
2338 item = self.__action_button_popup.Append(-1, _('Export to &file'))
2339 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item)
2340 self.__action_button_popup.Enable(id = menu_id, enable = False)
2341
2342 item = self.__action_button_popup.Append(-1, _('Export to &clipboard'))
2343 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item)
2344 self.__action_button_popup.Enable(id = menu_id, enable = False)
2345
2346 item = self.__action_button_popup.Append(-1, _('&Delete'))
2347 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
2348
2349
2350
2351
2352 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
2353 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
2354
2355 self._GRID_results_battery.show_by_panel = True
2356 self._GRID_results_battery.panel_to_show = None
2357
2358 self._PNL_results_battery_grid.Hide()
2359 self._BTN_display_mode.SetLabel(_('All: by &Day'))
2360
2361 self._PNL_results_all_grid.Show()
2362 self._PNL_results_all_listed.Hide()
2363 self.Layout()
2364
2365 self._PRW_panel.SetFocus()
2366
2367
2368
2370 pat = gmPerson.gmCurrentPatient()
2371 if pat.connected:
2372 self._GRID_results_battery.patient = pat
2373 if self.__display_mode == 'grid':
2374 self._GRID_results_all.patient = pat
2375 self._PNL_results_all_listed.patient = None
2376 else:
2377 self._GRID_results_all.patient = None
2378 self._PNL_results_all_listed.patient = pat
2379 else:
2380 self._GRID_results_battery.patient = None
2381 self._GRID_results_all.patient = None
2382 self._PNL_results_all_listed.patient = None
2383 return True
2384
2385
2386
2387
2389
2390 if tests is None:
2391 return True
2392
2393 if len(tests) == 0:
2394 return True
2395
2396 if parent is None:
2397 parent = wx.GetApp().GetTopWindow()
2398
2399 if len(tests) > 10:
2400 test_count = len(tests)
2401 tests2show = None
2402 else:
2403 test_count = None
2404 tests2show = tests
2405 if len(tests) == 0:
2406 return True
2407
2408 dlg = cMeasurementsReviewDlg(parent, -1, tests = tests, test_count = test_count)
2409 decision = dlg.ShowModal()
2410 if decision != wx.ID_APPLY:
2411 return True
2412
2413 wx.BeginBusyCursor()
2414 if dlg._RBTN_confirm_abnormal.GetValue():
2415 abnormal = None
2416 elif dlg._RBTN_results_normal.GetValue():
2417 abnormal = False
2418 else:
2419 abnormal = True
2420
2421 if dlg._RBTN_confirm_relevance.GetValue():
2422 relevant = None
2423 elif dlg._RBTN_results_not_relevant.GetValue():
2424 relevant = False
2425 else:
2426 relevant = True
2427
2428 comment = None
2429 if len(tests) == 1:
2430 comment = dlg._TCTRL_comment.GetValue()
2431
2432 make_responsible = dlg._CHBOX_responsible.IsChecked()
2433 dlg.Destroy()
2434
2435 for test in tests:
2436 test.set_review (
2437 technically_abnormal = abnormal,
2438 clinically_relevant = relevant,
2439 comment = comment,
2440 make_me_responsible = make_responsible
2441 )
2442 wx.EndBusyCursor()
2443
2444 return True
2445
2446
2447 from Gnumed.wxGladeWidgets import wxgMeasurementsReviewDlg
2448
2450
2452
2453 try:
2454 tests = kwargs['tests']
2455 del kwargs['tests']
2456 test_count = len(tests)
2457 try: del kwargs['test_count']
2458 except KeyError: pass
2459 except KeyError:
2460 tests = None
2461 test_count = kwargs['test_count']
2462 del kwargs['test_count']
2463
2464 wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs)
2465
2466 if tests is None:
2467 msg = _('%s results selected. Too many to list individually.') % test_count
2468 else:
2469 msg = '\n'.join (
2470 [ '%s: %s %s (%s)' % (
2471 t['unified_abbrev'],
2472 t['unified_val'],
2473 t['val_unit'],
2474 gmDateTime.pydt_strftime(t['clin_when'], '%Y %b %d')
2475 ) for t in tests
2476 ]
2477 )
2478
2479 self._LBL_tests.SetLabel(msg)
2480
2481 if test_count == 1:
2482 self._TCTRL_comment.Enable(True)
2483 self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], ''))
2484 if tests[0]['you_are_responsible']:
2485 self._CHBOX_responsible.Enable(False)
2486
2487 self.Fit()
2488
2489
2490
2496
2497
2498 from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl
2499
2500 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
2501 """This edit area saves *new* measurements into the active patient only."""
2502
2519
2520
2521
2522
2524 try:
2525 self._PRW_test.SetData(data = fields['pk_test_type']['data'])
2526 except KeyError:
2527 pass
2528 try:
2529 self._DPRW_evaluated.SetData(data = fields['clin_when']['data'])
2530 except KeyError:
2531 pass
2532 try:
2533 self._PRW_problem.SetData(data = fields['pk_episode']['data'])
2534 except KeyError:
2535 pass
2536 try:
2537 self._PRW_units.SetText(fields['val_unit']['data'], fields['val_unit']['data'], True)
2538 except KeyError:
2539 pass
2540 try:
2541 self._TCTRL_normal_min.SetValue(fields['val_normal_min']['data'])
2542 except KeyError:
2543 pass
2544 try:
2545 self._TCTRL_normal_max.SetValue(fields['val_normal_max']['data'])
2546 except KeyError:
2547 pass
2548 try:
2549 self._TCTRL_normal_range.SetValue(fields['val_normal_range']['data'])
2550 except KeyError:
2551 pass
2552 try:
2553 self._TCTRL_target_min.SetValue(fields['val_target_min']['data'])
2554 except KeyError:
2555 pass
2556 try:
2557 self._TCTRL_target_max.SetValue(fields['val_target_max']['data'])
2558 except KeyError:
2559 pass
2560 try:
2561 self._TCTRL_target_range.SetValue(fields['val_target_range']['data'])
2562 except KeyError:
2563 pass
2564
2565 self._TCTRL_result.SetFocus()
2566
2567
2599
2601 self._PRW_test.SetData(data = self.data['pk_test_type'])
2602 self.__refresh_loinc_info()
2603 self.__refresh_previous_value()
2604 self.__update_units_context()
2605 self._TCTRL_result.SetValue(self.data['unified_val'])
2606 self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True)
2607 self._PRW_abnormality_indicator.SetText (
2608 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2609 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2610 True
2611 )
2612 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2613 self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], ''))
2614 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2615 self._PRW_problem.SetData(self.data['pk_episode'])
2616 self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], ''))
2617 self._CHBOX_review.SetValue(False)
2618 self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False))
2619 self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False))
2620 self._CHBOX_abnormal.Enable(False)
2621 self._CHBOX_relevant.Enable(False)
2622 self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], ''))
2623 self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(self.data['val_normal_min'], '')))
2624 self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(self.data['val_normal_max'], '')))
2625 self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], ''))
2626 self._TCTRL_target_min.SetValue(str(gmTools.coalesce(self.data['val_target_min'], '')))
2627 self._TCTRL_target_max.SetValue(str(gmTools.coalesce(self.data['val_target_max'], '')))
2628 self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], ''))
2629 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], ''))
2630
2631 self._TCTRL_result.SetFocus()
2632
2634 self._PRW_test.SetText('', None, True)
2635 self.__refresh_loinc_info()
2636 self.__refresh_previous_value()
2637 self.__update_units_context()
2638 self._TCTRL_result.SetValue('')
2639 self._PRW_units.SetText('', None, True)
2640 self._PRW_abnormality_indicator.SetText('', None, True)
2641 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2642 self._TCTRL_note_test_org.SetValue('')
2643 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2644 self._PRW_problem.SetData(self.data['pk_episode'])
2645 self._TCTRL_narrative.SetValue('')
2646 self._CHBOX_review.SetValue(False)
2647 self._CHBOX_abnormal.SetValue(False)
2648 self._CHBOX_relevant.SetValue(False)
2649 self._CHBOX_abnormal.Enable(False)
2650 self._CHBOX_relevant.Enable(False)
2651 self._TCTRL_review_comment.SetValue('')
2652 self._TCTRL_normal_min.SetValue('')
2653 self._TCTRL_normal_max.SetValue('')
2654 self._TCTRL_normal_range.SetValue('')
2655 self._TCTRL_target_min.SetValue('')
2656 self._TCTRL_target_max.SetValue('')
2657 self._TCTRL_target_range.SetValue('')
2658 self._TCTRL_norm_ref_group.SetValue('')
2659
2660 self._PRW_test.SetFocus()
2661
2663
2664 validity = True
2665
2666 if not self._DPRW_evaluated.is_valid_timestamp():
2667 self._DPRW_evaluated.display_as_valid(False)
2668 validity = False
2669 else:
2670 self._DPRW_evaluated.display_as_valid(True)
2671
2672 val = self._TCTRL_result.GetValue().strip()
2673 if val == '':
2674 validity = False
2675 self.display_ctrl_as_valid(self._TCTRL_result, False)
2676 else:
2677 self.display_ctrl_as_valid(self._TCTRL_result, True)
2678 numeric, val = gmTools.input2decimal(val)
2679 if numeric:
2680 if self._PRW_units.GetValue().strip() == '':
2681 self._PRW_units.display_as_valid(False)
2682 validity = False
2683 else:
2684 self._PRW_units.display_as_valid(True)
2685 else:
2686 self._PRW_units.display_as_valid(True)
2687
2688 if self._PRW_problem.GetValue().strip() == '':
2689 self._PRW_problem.display_as_valid(False)
2690 validity = False
2691 else:
2692 self._PRW_problem.display_as_valid(True)
2693
2694 if self._PRW_test.GetValue().strip() == '':
2695 self._PRW_test.display_as_valid(False)
2696 validity = False
2697 else:
2698 self._PRW_test.display_as_valid(True)
2699
2700 if self._PRW_intended_reviewer.GetData() is None:
2701 self._PRW_intended_reviewer.display_as_valid(False)
2702 validity = False
2703 else:
2704 self._PRW_intended_reviewer.display_as_valid(True)
2705
2706 ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max]
2707 for widget in ctrls:
2708 val = widget.GetValue().strip()
2709 if val == '':
2710 continue
2711 try:
2712 decimal.Decimal(val.replace(',', '.', 1))
2713 self.display_ctrl_as_valid(widget, True)
2714 except:
2715 validity = False
2716 self.display_ctrl_as_valid(widget, False)
2717
2718 if validity is False:
2719 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save result. Invalid or missing essential input.'))
2720
2721 return validity
2722
2724
2725 emr = gmPerson.gmCurrentPatient().emr
2726
2727 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
2728 if success:
2729 v_num = result
2730 v_al = None
2731 else:
2732 v_al = self._TCTRL_result.GetValue().strip()
2733 v_num = None
2734
2735 pk_type = self._PRW_test.GetData()
2736 if pk_type is None:
2737 abbrev = self._PRW_test.GetValue().strip()
2738 name = self._PRW_test.GetValue().strip()
2739 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
2740 lab = manage_measurement_orgs (
2741 parent = self,
2742 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
2743 )
2744 if lab is not None:
2745 lab = lab['pk_test_org']
2746 tt = gmPathLab.create_measurement_type (
2747 lab = lab,
2748 abbrev = abbrev,
2749 name = name,
2750 unit = unit
2751 )
2752 pk_type = tt['pk_test_type']
2753
2754 tr = emr.add_test_result (
2755 episode = self._PRW_problem.GetData(can_create=True, is_open=False),
2756 type = pk_type,
2757 intended_reviewer = self._PRW_intended_reviewer.GetData(),
2758 val_num = v_num,
2759 val_alpha = v_al,
2760 unit = self._PRW_units.GetValue()
2761 )
2762
2763 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
2764
2765 ctrls = [
2766 ('abnormality_indicator', self._PRW_abnormality_indicator),
2767 ('note_test_org', self._TCTRL_note_test_org),
2768 ('comment', self._TCTRL_narrative),
2769 ('val_normal_range', self._TCTRL_normal_range),
2770 ('val_target_range', self._TCTRL_target_range),
2771 ('norm_ref_group', self._TCTRL_norm_ref_group)
2772 ]
2773 for field, widget in ctrls:
2774 tr[field] = widget.GetValue().strip()
2775
2776 ctrls = [
2777 ('val_normal_min', self._TCTRL_normal_min),
2778 ('val_normal_max', self._TCTRL_normal_max),
2779 ('val_target_min', self._TCTRL_target_min),
2780 ('val_target_max', self._TCTRL_target_max)
2781 ]
2782 for field, widget in ctrls:
2783 val = widget.GetValue().strip()
2784 if val == '':
2785 tr[field] = None
2786 else:
2787 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
2788
2789 tr.save_payload()
2790
2791 if self._CHBOX_review.GetValue() is True:
2792 tr.set_review (
2793 technically_abnormal = self._CHBOX_abnormal.GetValue(),
2794 clinically_relevant = self._CHBOX_relevant.GetValue(),
2795 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
2796 make_me_responsible = False
2797 )
2798
2799 self.data = tr
2800
2801
2802
2803
2804
2805
2806
2807
2808 return True
2809
2811
2812 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
2813 if success:
2814 v_num = result
2815 v_al = None
2816 else:
2817 v_num = None
2818 v_al = self._TCTRL_result.GetValue().strip()
2819
2820 pk_type = self._PRW_test.GetData()
2821 if pk_type is None:
2822 abbrev = self._PRW_test.GetValue().strip()
2823 name = self._PRW_test.GetValue().strip()
2824 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
2825 lab = manage_measurement_orgs (
2826 parent = self,
2827 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
2828 )
2829 if lab is not None:
2830 lab = lab['pk_test_org']
2831 tt = gmPathLab.create_measurement_type (
2832 lab = None,
2833 abbrev = abbrev,
2834 name = name,
2835 unit = unit
2836 )
2837 pk_type = tt['pk_test_type']
2838
2839 tr = self.data
2840
2841 tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False)
2842 tr['pk_test_type'] = pk_type
2843 tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData()
2844 tr['val_num'] = v_num
2845 tr['val_alpha'] = v_al
2846 tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
2847 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
2848
2849 ctrls = [
2850 ('abnormality_indicator', self._PRW_abnormality_indicator),
2851 ('note_test_org', self._TCTRL_note_test_org),
2852 ('comment', self._TCTRL_narrative),
2853 ('val_normal_range', self._TCTRL_normal_range),
2854 ('val_target_range', self._TCTRL_target_range),
2855 ('norm_ref_group', self._TCTRL_norm_ref_group)
2856 ]
2857 for field, widget in ctrls:
2858 tr[field] = widget.GetValue().strip()
2859
2860 ctrls = [
2861 ('val_normal_min', self._TCTRL_normal_min),
2862 ('val_normal_max', self._TCTRL_normal_max),
2863 ('val_target_min', self._TCTRL_target_min),
2864 ('val_target_max', self._TCTRL_target_max)
2865 ]
2866 for field, widget in ctrls:
2867 val = widget.GetValue().strip()
2868 if val == '':
2869 tr[field] = None
2870 else:
2871 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
2872
2873 tr.save_payload()
2874
2875 if self._CHBOX_review.GetValue() is True:
2876 tr.set_review (
2877 technically_abnormal = self._CHBOX_abnormal.GetValue(),
2878 clinically_relevant = self._CHBOX_relevant.GetValue(),
2879 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
2880 make_me_responsible = False
2881 )
2882
2883
2884
2885
2886
2887
2888
2889
2890 return True
2891
2892
2893
2898
2900 self.__refresh_loinc_info()
2901 self.__refresh_previous_value()
2902 self.__update_units_context()
2903
2904 self.__update_normal_range()
2905 self.__update_clinical_range()
2906
2908
2909 self.__update_normal_range()
2910 self.__update_clinical_range()
2911
2913
2914 if not self._CHBOX_review.GetValue():
2915 self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != '')
2916
2921
2937
2941
2942
2943
2945
2946 if self._PRW_test.GetData() is None:
2947 self._PRW_units.unset_context(context = 'pk_type')
2948 self._PRW_units.unset_context(context = 'loinc')
2949 if self._PRW_test.GetValue().strip() == '':
2950 self._PRW_units.unset_context(context = 'test_name')
2951 else:
2952 self._PRW_units.set_context(context = 'test_name', val = self._PRW_test.GetValue().strip())
2953 return
2954
2955 tt = self._PRW_test.GetData(as_instance = True)
2956
2957 self._PRW_units.set_context(context = 'pk_type', val = tt['pk_test_type'])
2958 self._PRW_units.set_context(context = 'test_name', val = tt['name'])
2959
2960 if tt['loinc'] is not None:
2961 self._PRW_units.set_context(context = 'loinc', val = tt['loinc'])
2962
2963
2964 if self._PRW_units.GetValue().strip() == '':
2965 clin_when = self._DPRW_evaluated.GetData()
2966 if clin_when is None:
2967 unit = tt.temporally_closest_unit
2968 else:
2969 clin_when = clin_when.get_pydt()
2970 unit = tt.get_temporally_closest_unit(timestamp = clin_when)
2971 if unit is None:
2972 self._PRW_units.SetText('', unit, True)
2973 else:
2974 self._PRW_units.SetText(unit, unit, True)
2975
2976
2997
2998
3019
3020
3022
3023 self._TCTRL_loinc.SetValue('')
3024
3025 if self._PRW_test.GetData() is None:
3026 return
3027
3028 tt = self._PRW_test.GetData(as_instance = True)
3029
3030 if tt['loinc'] is None:
3031 return
3032
3033 info = gmLOINC.loinc2term(loinc = tt['loinc'])
3034 if len(info) == 0:
3035 self._TCTRL_loinc.SetValue('')
3036 return
3037
3038 self._TCTRL_loinc.SetValue('%s: %s' % (tt['loinc'], info[0]))
3039
3041 self._TCTRL_previous_value.SetValue('')
3042
3043
3044 if self.data is not None:
3045 return
3046 if self._PRW_test.GetData() is None:
3047 return
3048 tt = self._PRW_test.GetData(as_instance = True)
3049 most_recent = tt.get_most_recent_results (
3050 no_of_results = 1,
3051 patient = gmPerson.gmCurrentPatient().ID
3052 )
3053 if most_recent is None:
3054 return
3055 self._TCTRL_previous_value.SetValue(_('%s ago: %s%s%s - %s%s') % (
3056 gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - most_recent['clin_when']),
3057 most_recent['unified_val'],
3058 most_recent['val_unit'],
3059 gmTools.coalesce(most_recent['abnormality_indicator'], '', ' (%s)'),
3060 most_recent['abbrev_tt'],
3061 gmTools.coalesce(most_recent.formatted_range, '', ' [%s]')
3062 ))
3063 self._TCTRL_previous_value.SetToolTip(most_recent.format (
3064 with_review = True,
3065 with_evaluation = False,
3066 with_ranges = True,
3067 with_episode = True,
3068 with_type_details=True
3069 ))
3070
3071
3072
3073
3075
3076 if parent is None:
3077 parent = wx.GetApp().GetTopWindow()
3078
3079 if msg is None:
3080 msg = _('Pick the relevant measurement types.')
3081
3082 if right_column is None:
3083 right_columns = [_('Picked')]
3084 else:
3085 right_columns = [right_column]
3086
3087 picker = gmListWidgets.cItemPickerDlg(parent, -1, msg = msg)
3088 picker.set_columns(columns = [_('Known measurement types')], columns_right = right_columns)
3089 types = gmPathLab.get_measurement_types(order_by = 'unified_abbrev')
3090 picker.set_choices (
3091 choices = [
3092 '%s: %s%s' % (
3093 t['unified_abbrev'],
3094 t['unified_name'],
3095 gmTools.coalesce(t['name_org'], '', ' (%s)')
3096 )
3097 for t in types
3098 ],
3099 data = types
3100 )
3101 if picks is not None:
3102 picker.set_picks (
3103 picks = [
3104 '%s: %s%s' % (
3105 p['unified_abbrev'],
3106 p['unified_name'],
3107 gmTools.coalesce(p['name_org'], '', ' (%s)')
3108 )
3109 for p in picks
3110 ],
3111 data = picks
3112 )
3113 result = picker.ShowModal()
3114
3115 if result == wx.ID_CANCEL:
3116 picker.Destroy()
3117 return None
3118
3119 picks = picker.picks
3120 picker.Destroy()
3121 return picks
3122
3123
3146
3147 def delete(measurement_type):
3148 if measurement_type.in_use:
3149 gmDispatcher.send (
3150 signal = 'statustext',
3151 beep = True,
3152 msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev'])
3153 )
3154 return False
3155 gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type'])
3156 return True
3157
3158 def get_tooltip(test_type):
3159 return test_type.format()
3160
3161 def manage_aggregates(test_type):
3162 manage_meta_test_types(parent = parent)
3163 return False
3164
3165 def refresh(lctrl):
3166 mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev')
3167 items = [ [
3168 m['abbrev'],
3169 m['name'],
3170 gmTools.coalesce(m['reference_unit'], ''),
3171 gmTools.coalesce(m['loinc'], ''),
3172 gmTools.coalesce(m['comment_type'], ''),
3173 gmTools.coalesce(m['name_org'], '?'),
3174 gmTools.coalesce(m['comment_org'], ''),
3175 m['pk_test_type']
3176 ] for m in mtypes ]
3177 lctrl.set_string_items(items)
3178 lctrl.set_data(mtypes)
3179
3180 msg = _(
3181 '\n'
3182 'These are the measurement types currently defined in GNUmed.\n'
3183 '\n'
3184 )
3185
3186 gmListWidgets.get_choices_from_list (
3187 parent = parent,
3188 msg = msg,
3189 caption = _('Showing measurement types.'),
3190 columns = [ _('Abbrev'), _('Name'), _('Unit'), _('LOINC'), _('Comment'), _('Org'), _('Comment'), '#' ],
3191 single_selection = True,
3192 refresh_callback = refresh,
3193 edit_callback = edit,
3194 new_callback = edit,
3195 delete_callback = delete,
3196 list_tooltip_callback = get_tooltip,
3197 left_extra_button = (_('%s &Aggregate') % gmTools.u_sum, _('Manage aggregations (%s) of tests into groups.') % gmTools.u_sum, manage_aggregates)
3198 )
3199
3200
3202
3204
3205 query = """
3206 SELECT DISTINCT ON (field_label)
3207 pk_test_type AS data,
3208 name
3209 || ' ('
3210 || coalesce (
3211 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
3212 '%(in_house)s'
3213 )
3214 || ')'
3215 AS field_label,
3216 name
3217 || ' ('
3218 || abbrev || ', '
3219 || coalesce(abbrev_meta || ': ' || name_meta || ', ', '')
3220 || coalesce (
3221 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
3222 '%(in_house)s'
3223 )
3224 || ')'
3225 AS list_label
3226 FROM
3227 clin.v_test_types c_vtt
3228 WHERE
3229 abbrev_meta %%(fragment_condition)s
3230 OR
3231 name_meta %%(fragment_condition)s
3232 OR
3233 abbrev %%(fragment_condition)s
3234 OR
3235 name %%(fragment_condition)s
3236 ORDER BY field_label
3237 LIMIT 50""" % {'in_house': _('generic / in house lab')}
3238
3239 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3240 mp.setThresholds(1, 2, 4)
3241 mp.word_separators = '[ \t:@]+'
3242 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
3243 self.matcher = mp
3244 self.SetToolTip(_('Select the type of measurement.'))
3245 self.selection_only = False
3246
3247
3253
3254
3255 from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl
3256
3257 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
3258
3275
3276
3278
3279
3280 query = """
3281 select distinct on (name)
3282 pk,
3283 name
3284 from clin.test_type
3285 where
3286 name %(fragment_condition)s
3287 order by name
3288 limit 50"""
3289 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3290 mp.setThresholds(1, 2, 4)
3291 self._PRW_name.matcher = mp
3292 self._PRW_name.selection_only = False
3293 self._PRW_name.add_callback_on_lose_focus(callback = self._on_name_lost_focus)
3294
3295
3296 query = """
3297 select distinct on (abbrev)
3298 pk,
3299 abbrev
3300 from clin.test_type
3301 where
3302 abbrev %(fragment_condition)s
3303 order by abbrev
3304 limit 50"""
3305 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3306 mp.setThresholds(1, 2, 3)
3307 self._PRW_abbrev.matcher = mp
3308 self._PRW_abbrev.selection_only = False
3309
3310
3311 self._PRW_reference_unit.selection_only = False
3312
3313
3314 mp = gmLOINC.cLOINCMatchProvider()
3315 mp.setThresholds(1, 2, 4)
3316
3317
3318 self._PRW_loinc.matcher = mp
3319 self._PRW_loinc.selection_only = False
3320 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
3321
3322
3324
3325 test = self._PRW_name.GetValue().strip()
3326
3327 if test == '':
3328 self._PRW_reference_unit.unset_context(context = 'test_name')
3329 return
3330
3331 self._PRW_reference_unit.set_context(context = 'test_name', val = test)
3332
3334 loinc = self._PRW_loinc.GetData()
3335
3336 if loinc is None:
3337 self._TCTRL_loinc_info.SetValue('')
3338 self._PRW_reference_unit.unset_context(context = 'loinc')
3339 return
3340
3341 self._PRW_reference_unit.set_context(context = 'loinc', val = loinc)
3342
3343 info = gmLOINC.loinc2term(loinc = loinc)
3344 if len(info) == 0:
3345 self._TCTRL_loinc_info.SetValue('')
3346 return
3347
3348 self._TCTRL_loinc_info.SetValue(info[0])
3349
3350
3351
3353
3354 has_errors = False
3355 for field in [self._PRW_name, self._PRW_abbrev, self._PRW_reference_unit]:
3356 if field.GetValue().strip() in ['', None]:
3357 has_errors = True
3358 field.display_as_valid(valid = False)
3359 else:
3360 field.display_as_valid(valid = True)
3361 field.Refresh()
3362
3363 return (not has_errors)
3364
3394
3421
3423 self._PRW_name.SetText('', None, True)
3424 self._on_name_lost_focus()
3425 self._PRW_abbrev.SetText('', None, True)
3426 self._PRW_reference_unit.SetText('', None, True)
3427 self._PRW_loinc.SetText('', None, True)
3428 self._on_loinc_lost_focus()
3429 self._TCTRL_comment_type.SetValue('')
3430 self._PRW_test_org.SetText('', None, True)
3431 self._PRW_meta_type.SetText('', None, True)
3432
3433 self._PRW_name.SetFocus()
3434
3436 self._PRW_name.SetText(self.data['name'], self.data['name'], True)
3437 self._on_name_lost_focus()
3438 self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True)
3439 self._PRW_reference_unit.SetText (
3440 gmTools.coalesce(self.data['reference_unit'], ''),
3441 self.data['reference_unit'],
3442 True
3443 )
3444 self._PRW_loinc.SetText (
3445 gmTools.coalesce(self.data['loinc'], ''),
3446 self.data['loinc'],
3447 True
3448 )
3449 self._on_loinc_lost_focus()
3450 self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], ''))
3451 self._PRW_test_org.SetText (
3452 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
3453 self.data['pk_test_org'],
3454 True
3455 )
3456 if self.data['pk_meta_test_type'] is None:
3457 self._PRW_meta_type.SetText('', None, True)
3458 else:
3459 self._PRW_meta_type.SetText('%s: %s' % (self.data['abbrev_meta'], self.data['name_meta']), self.data['pk_meta_test_type'], True)
3460
3461 self._PRW_name.SetFocus()
3462
3464 self._refresh_as_new()
3465 self._PRW_test_org.SetText (
3466 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
3467 self.data['pk_test_org'],
3468 True
3469 )
3470 self._PRW_name.SetFocus()
3471
3472
3473 _SQL_units_from_test_results = """
3474 -- via clin.v_test_results.pk_type (for types already used in results)
3475 SELECT
3476 val_unit AS data,
3477 val_unit AS field_label,
3478 val_unit || ' (' || name_tt || ')' AS list_label,
3479 1 AS rank
3480 FROM
3481 clin.v_test_results
3482 WHERE
3483 (
3484 val_unit %(fragment_condition)s
3485 OR
3486 reference_unit %(fragment_condition)s
3487 )
3488 %(ctxt_type_pk)s
3489 %(ctxt_test_name)s
3490 """
3491
3492 _SQL_units_from_test_types = """
3493 -- via clin.test_type (for types not yet used in results)
3494 SELECT
3495 reference_unit AS data,
3496 reference_unit AS field_label,
3497 reference_unit || ' (' || name || ')' AS list_label,
3498 2 AS rank
3499 FROM
3500 clin.test_type
3501 WHERE
3502 reference_unit %(fragment_condition)s
3503 %(ctxt_ctt)s
3504 """
3505
3506 _SQL_units_from_loinc_ipcc = """
3507 -- via ref.loinc.ipcc_units
3508 SELECT
3509 ipcc_units AS data,
3510 ipcc_units AS field_label,
3511 ipcc_units || ' (LOINC.ipcc: ' || term || ')' AS list_label,
3512 3 AS rank
3513 FROM
3514 ref.loinc
3515 WHERE
3516 ipcc_units %(fragment_condition)s
3517 %(ctxt_loinc)s
3518 %(ctxt_loinc_term)s
3519 """
3520
3521 _SQL_units_from_loinc_submitted = """
3522 -- via ref.loinc.submitted_units
3523 SELECT
3524 submitted_units AS data,
3525 submitted_units AS field_label,
3526 submitted_units || ' (LOINC.submitted:' || term || ')' AS list_label,
3527 3 AS rank
3528 FROM
3529 ref.loinc
3530 WHERE
3531 submitted_units %(fragment_condition)s
3532 %(ctxt_loinc)s
3533 %(ctxt_loinc_term)s
3534 """
3535
3536 _SQL_units_from_loinc_example = """
3537 -- via ref.loinc.example_units
3538 SELECT
3539 example_units AS data,
3540 example_units AS field_label,
3541 example_units || ' (LOINC.example: ' || term || ')' AS list_label,
3542 3 AS rank
3543 FROM
3544 ref.loinc
3545 WHERE
3546 example_units %(fragment_condition)s
3547 %(ctxt_loinc)s
3548 %(ctxt_loinc_term)s
3549 """
3550
3551 _SQL_units_from_substance_doses = """
3552 -- via ref.v_substance_doses.unit
3553 SELECT
3554 unit AS data,
3555 unit AS field_label,
3556 unit || ' (' || substance || ')' AS list_label,
3557 2 AS rank
3558 FROM
3559 ref.v_substance_doses
3560 WHERE
3561 unit %(fragment_condition)s
3562 %(ctxt_substance)s
3563 """
3564
3565 _SQL_units_from_substance_doses2 = """
3566 -- via ref.v_substance_doses.dose_unit
3567 SELECT
3568 dose_unit AS data,
3569 dose_unit AS field_label,
3570 dose_unit || ' (' || substance || ')' AS list_label,
3571 2 AS rank
3572 FROM
3573 ref.v_substance_doses
3574 WHERE
3575 dose_unit %(fragment_condition)s
3576 %(ctxt_substance)s
3577 """
3578
3579
3581
3583
3584 query = """
3585 SELECT DISTINCT ON (data)
3586 data,
3587 field_label,
3588 list_label
3589 FROM (
3590
3591 SELECT
3592 data,
3593 field_label,
3594 list_label,
3595 rank
3596 FROM (
3597 (%s) UNION ALL
3598 (%s) UNION ALL
3599 (%s) UNION ALL
3600 (%s) UNION ALL
3601 (%s) UNION ALL
3602 (%s) UNION ALL
3603 (%s)
3604 ) AS all_matching_units
3605 WHERE data IS NOT NULL
3606 ORDER BY rank, list_label
3607
3608 ) AS ranked_matching_units
3609 LIMIT 50""" % (
3610 _SQL_units_from_test_results,
3611 _SQL_units_from_test_types,
3612 _SQL_units_from_loinc_ipcc,
3613 _SQL_units_from_loinc_submitted,
3614 _SQL_units_from_loinc_example,
3615 _SQL_units_from_substance_doses,
3616 _SQL_units_from_substance_doses2
3617 )
3618
3619 ctxt = {
3620 'ctxt_type_pk': {
3621 'where_part': 'AND pk_test_type = %(pk_type)s',
3622 'placeholder': 'pk_type'
3623 },
3624 'ctxt_test_name': {
3625 'where_part': 'AND %(test_name)s IN (name_tt, name_meta, abbrev_meta)',
3626 'placeholder': 'test_name'
3627 },
3628 'ctxt_ctt': {
3629 'where_part': 'AND %(test_name)s IN (name, abbrev)',
3630 'placeholder': 'test_name'
3631 },
3632 'ctxt_loinc': {
3633 'where_part': 'AND code = %(loinc)s',
3634 'placeholder': 'loinc'
3635 },
3636 'ctxt_loinc_term': {
3637 'where_part': 'AND term ~* %(test_name)s',
3638 'placeholder': 'test_name'
3639 },
3640 'ctxt_substance': {
3641 'where_part': 'AND description ~* %(substance)s',
3642 'placeholder': 'substance'
3643 }
3644 }
3645
3646 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = ctxt)
3647 mp.setThresholds(1, 2, 4)
3648
3649 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
3650 self.matcher = mp
3651 self.SetToolTip(_('Select the desired unit for the amount or measurement.'))
3652 self.selection_only = False
3653 self.phrase_separators = '[;|]+'
3654
3655
3656
3657
3659
3661
3662 query = """
3663 select distinct abnormality_indicator,
3664 abnormality_indicator, abnormality_indicator
3665 from clin.v_test_results
3666 where
3667 abnormality_indicator %(fragment_condition)s
3668 order by abnormality_indicator
3669 limit 25"""
3670
3671 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3672 mp.setThresholds(1, 1, 2)
3673 mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"'
3674 mp.word_separators = '[ \t&:]+'
3675 gmPhraseWheel.cPhraseWheel.__init__ (
3676 self,
3677 *args,
3678 **kwargs
3679 )
3680 self.matcher = mp
3681 self.SetToolTip(_('Select an indicator for the level of abnormality.'))
3682 self.selection_only = False
3683
3684
3685
3686
3698
3707
3708 def refresh(lctrl):
3709 orgs = gmPathLab.get_test_orgs()
3710 lctrl.set_string_items ([
3711 (o['unit'], o['organization'], gmTools.coalesce(o['test_org_contact'], ''), gmTools.coalesce(o['comment'], ''), o['pk_test_org'])
3712 for o in orgs
3713 ])
3714 lctrl.set_data(orgs)
3715
3716 def delete(test_org):
3717 gmPathLab.delete_test_org(test_org = test_org['pk_test_org'])
3718 return True
3719
3720 if msg is None:
3721 msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n')
3722
3723 return gmListWidgets.get_choices_from_list (
3724 parent = parent,
3725 msg = msg,
3726 caption = _('Showing diagnostic orgs.'),
3727 columns = [_('Name'), _('Organization'), _('Contact'), _('Comment'), '#'],
3728 single_selection = True,
3729 refresh_callback = refresh,
3730 edit_callback = edit,
3731 new_callback = edit,
3732 delete_callback = delete
3733 )
3734
3735
3736 from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl
3737
3738 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
3739
3755
3756
3757
3758
3759
3760
3761
3762
3764 has_errors = False
3765 if self._PRW_org_unit.GetData() is None:
3766 if self._PRW_org_unit.GetValue().strip() == '':
3767 has_errors = True
3768 self._PRW_org_unit.display_as_valid(valid = False)
3769 else:
3770 self._PRW_org_unit.display_as_valid(valid = True)
3771 else:
3772 self._PRW_org_unit.display_as_valid(valid = True)
3773
3774 return (not has_errors)
3775
3786
3806
3811
3816
3818 self._refresh_as_new()
3819
3822
3823
3825
3827
3828 query = """
3829 SELECT DISTINCT ON (list_label)
3830 pk_test_org AS data,
3831 unit || ' (' || organization || ')' AS field_label,
3832 unit || ' @ ' || organization AS list_label
3833 FROM clin.v_test_orgs
3834 WHERE
3835 unit %(fragment_condition)s
3836 OR
3837 organization %(fragment_condition)s
3838 ORDER BY list_label
3839 LIMIT 50"""
3840 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3841 mp.setThresholds(1, 2, 4)
3842
3843 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
3844 self.matcher = mp
3845 self.SetToolTip(_('The name of the path lab/diagnostic organisation.'))
3846 self.selection_only = False
3847
3860
3863
3864
3865
3866
3883
3884
3893
3894 def delete(meta_test_type):
3895 gmPathLab.delete_meta_type(meta_type = meta_test_type['pk'])
3896 return True
3897
3898 def get_tooltip(data):
3899 if data is None:
3900 return None
3901 return data.format(with_tests = True)
3902
3903 def refresh(lctrl):
3904 mtts = gmPathLab.get_meta_test_types()
3905 items = [ [
3906 m['abbrev'],
3907 m['name'],
3908 gmTools.coalesce(m['loinc'], ''),
3909 gmTools.coalesce(m['comment'], ''),
3910 m['pk']
3911 ] for m in mtts ]
3912 lctrl.set_string_items(items)
3913 lctrl.set_data(mtts)
3914
3915
3916 msg = _(
3917 '\n'
3918 'These are the meta test types currently defined in GNUmed.\n'
3919 '\n'
3920 'Meta test types allow you to aggregate several actual test types used\n'
3921 'by pathology labs into one logical type.\n'
3922 '\n'
3923 'This is useful for grouping together results of tests which come under\n'
3924 'different names but really are the same thing. This often happens when\n'
3925 'you switch labs or the lab starts using another test method.\n'
3926 )
3927
3928 gmListWidgets.get_choices_from_list (
3929 parent = parent,
3930 msg = msg,
3931 caption = _('Showing meta test types.'),
3932 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), '#'],
3933 single_selection = True,
3934 list_tooltip_callback = get_tooltip,
3935 edit_callback = edit,
3936 new_callback = edit,
3937 delete_callback = delete,
3938 refresh_callback = refresh
3939 )
3940
3941
3986
3987
3988 from Gnumed.wxGladeWidgets import wxgMetaTestTypeEAPnl
3989
4136
4137
4138
4139
4141 ea = cTestPanelEAPnl(parent, -1)
4142 ea.data = test_panel
4143 ea.mode = gmTools.coalesce(test_panel, 'new', 'edit')
4144 dlg = gmEditArea.cGenericEditAreaDlg2 (
4145 parent = parent,
4146 id = -1,
4147 edit_area = ea,
4148 single_entry = gmTools.bool2subst((test_panel is None), False, True)
4149 )
4150 dlg.SetTitle(gmTools.coalesce(test_panel, _('Adding new test panel'), _('Editing test panel')))
4151 if dlg.ShowModal() == wx.ID_OK:
4152 dlg.Destroy()
4153 return True
4154 dlg.Destroy()
4155 return False
4156
4157
4166
4167 def delete(test_panel):
4168 gmPathLab.delete_test_panel(pk = test_panel['pk_test_panel'])
4169 return True
4170
4171 def get_tooltip(test_panel):
4172 return test_panel.format()
4173
4174 def refresh(lctrl):
4175 panels = gmPathLab.get_test_panels(order_by = 'description')
4176 items = [ [
4177 p['description'],
4178 gmTools.coalesce(p['comment'], ''),
4179 p['pk_test_panel']
4180 ] for p in panels ]
4181 lctrl.set_string_items(items)
4182 lctrl.set_data(panels)
4183
4184 gmListWidgets.get_choices_from_list (
4185 parent = parent,
4186 caption = 'GNUmed: ' + _('Test panels list'),
4187 columns = [ _('Name'), _('Comment'), '#' ],
4188 single_selection = True,
4189 refresh_callback = refresh,
4190 edit_callback = edit,
4191 new_callback = edit,
4192 delete_callback = delete,
4193 list_tooltip_callback = get_tooltip
4194 )
4195
4196
4198
4200 query = """
4201 SELECT
4202 pk_test_panel
4203 AS data,
4204 description
4205 AS field_label,
4206 description
4207 AS list_label
4208 FROM
4209 clin.v_test_panels
4210 WHERE
4211 description %(fragment_condition)s
4212 ORDER BY field_label
4213 LIMIT 30"""
4214 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4215 mp.setThresholds(1, 2, 4)
4216
4217 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4218 self.matcher = mp
4219 self.SetToolTip(_('Select a test panel.'))
4220 self.selection_only = True
4221
4226
4231
4232
4233 from Gnumed.wxGladeWidgets import wxgTestPanelEAPnl
4234
4235 -class cTestPanelEAPnl(wxgTestPanelEAPnl.wxgTestPanelEAPnl, gmEditArea.cGenericEditAreaMixin):
4236
4256
4257
4259 self._LCTRL_loincs.set_columns([_('LOINC'), _('Term'), _('Units')])
4260 self._LCTRL_loincs.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
4261
4262 self._LCTRL_loincs.delete_callback = self._remove_loincs_from_list
4263 self.__refresh_loinc_list()
4264
4265 self._PRW_loinc.final_regex = r'.*'
4266 self._PRW_loinc.add_callback_on_selection(callback = self._on_loinc_selected)
4267
4268
4270 self._LCTRL_loincs.remove_items_safely()
4271 if self.__loincs is None:
4272 if self.data is None:
4273 return
4274 self.__loincs = self.data['loincs']
4275
4276 items = []
4277 for loinc in self.__loincs:
4278 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4279 if len(loinc_detail) == 0:
4280
4281 ttypes = gmPathLab.get_measurement_types(loincs = [loinc])
4282 if len(ttypes) == 0:
4283 items.append([loinc, _('LOINC not found'), ''])
4284 else:
4285 for tt in ttypes:
4286 items.append([loinc, _('not a LOINC') + u'; %(name)s @ %(name_org)s [#%(pk_test_type)s]' % tt, ''])
4287 continue
4288 items.append ([
4289 loinc,
4290 loinc_detail['term'],
4291 gmTools.coalesce(loinc_detail['example_units'], '', '%s')
4292 ])
4293
4294 self._LCTRL_loincs.set_string_items(items)
4295 self._LCTRL_loincs.set_column_widths()
4296
4297
4298
4299
4301 validity = True
4302
4303 if self.__loincs is None:
4304 if self.data is not None:
4305 self.__loincs = self.data['loincs']
4306
4307 if self.__loincs is None:
4308
4309 self.status_message = _('No LOINC codes selected.')
4310 self._PRW_loinc.SetFocus()
4311
4312 if self._TCTRL_description.GetValue().strip() == '':
4313 validity = False
4314 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False)
4315 self._TCTRL_description.SetFocus()
4316 else:
4317 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True)
4318
4319 return validity
4320
4321
4330
4331
4333 self.data['description'] = self._TCTRL_description.GetValue().strip()
4334 self.data['comment'] = self._TCTRL_comment.GetValue().strip()
4335 self.data.save()
4336 if self.__loincs is not None:
4337 self.data.included_loincs = self.__loincs
4338 return True
4339
4340
4342 self._TCTRL_description.SetValue('')
4343 self._TCTRL_comment.SetValue('')
4344 self._PRW_loinc.SetText('', None)
4345 self._LBL_loinc.SetLabel('')
4346 self.__loincs = None
4347 self.__refresh_loinc_list()
4348
4349 self._TCTRL_description.SetFocus()
4350
4351
4353 self._refresh_as_new()
4354
4355
4357 self._TCTRL_description.SetValue(self.data['description'])
4358 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
4359 self._PRW_loinc.SetText('', None)
4360 self._LBL_loinc.SetLabel('')
4361 self.__loincs = self.data['loincs']
4362 self.__refresh_loinc_list()
4363
4364 self._PRW_loinc.SetFocus()
4365
4366
4367
4368
4370 loinc = self._PRW_loinc.GetData()
4371 if loinc is None:
4372 self._LBL_loinc.SetLabel('')
4373 return
4374 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4375 if len(loinc_detail) == 0:
4376 loinc_str = _('no LOINC details found')
4377 else:
4378 loinc_str = '%s: %s%s' % (
4379 loinc,
4380 loinc_detail['term'],
4381 gmTools.coalesce(loinc_detail['example_units'], '', ' (%s)')
4382 )
4383 self._LBL_loinc.SetLabel(loinc_str)
4384
4385
4407
4408
4412
4413
4415 loincs2remove = self._LCTRL_loincs.selected_item_data
4416 if loincs2remove is None:
4417 return
4418 for loinc in loincs2remove:
4419 try:
4420 while True:
4421 self.__loincs.remove(loinc[0])
4422 except ValueError:
4423 pass
4424 self.__refresh_loinc_list()
4425
4426
4427
4428
4429 if __name__ == '__main__':
4430
4431 from Gnumed.pycommon import gmLog2
4432 from Gnumed.wxpython import gmPatSearchWidgets
4433
4434 gmI18N.activate_locale()
4435 gmI18N.install_domain()
4436 gmDateTime.init()
4437
4438
4446
4454
4455
4456
4457
4458
4459
4460
4461 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
4462
4463 test_test_ea_pnl()
4464
4465
4466
4467