| Home | Trees | Indices | Help |
|
|---|
|
|
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 # HL7 related widgets
63 #================================================================
65
66 if parent is None:
67 parent = wx.GetApp().GetTopWindow()
68
69 # select file
70 paths = gmTools.gmPaths()
71 dlg = wx.FileDialog (
72 parent = parent,
73 message = _('Show HL7 file:'),
74 # make configurable:
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 # select file
101 paths = gmTools.gmPaths()
102 dlg = wx.FileDialog (
103 parent = parent,
104 message = _('Extract HL7 from XML file:'),
105 # make configurable:
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 # make configurable:
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 #================================================================
192
193 if parent is None:
194 parent = wx.GetApp().GetTopWindow()
195 #------------------------------------------------------------
196 def show_hl7(staged_item):
197 if staged_item is None:
198 return False
199 if 'HL7' not in staged_item['data_type']:
200 return False
201 filename = staged_item.save_to_file()
202 if filename is None:
203 filename = gmTools.get_unique_filename()
204 tmp_file = io.open(filename, mode = 'at', encoding = 'utf8')
205 tmp_file.write('\n')
206 tmp_file.write('-' * 80)
207 tmp_file.write('\n')
208 tmp_file.write(gmTools.coalesce(staged_item['comment'], ''))
209 tmp_file.close()
210 gmMimeLib.call_viewer_on_file(aFile = filename, block = False)
211 return False
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 # edit_callback=None,
304 # new_callback=None,
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 # right_extra_button=None
309 )
310
311 #================================================================
312 # convenience functions
313 #================================================================
315
316 dbcfg = gmCfg.cCfgSQL()
317
318 url = dbcfg.get2 (
319 option = 'external.urls.measurements_search',
320 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
321 bias = 'user',
322 default = gmPathLab.URL_test_result_information_search
323 )
324
325 base_url = dbcfg.get2 (
326 option = 'external.urls.measurements_encyclopedia',
327 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
328 bias = 'user',
329 default = gmPathLab.URL_test_result_information
330 )
331
332 if measurement_type is None:
333 url = base_url
334
335 measurement_type = measurement_type.strip()
336
337 if measurement_type == '':
338 url = base_url
339
340 url = url % {'search_term': measurement_type}
341
342 gmNetworkTools.open_url_in_browser(url = url)
343
344 #----------------------------------------------------------------
346 ea = cMeasurementEditAreaPnl(parent, -1)
347 ea.data = measurement
348 ea.mode = gmTools.coalesce(measurement, 'new', 'edit')
349 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
350 dlg.SetTitle(gmTools.coalesce(measurement, _('Adding new measurement'), _('Editing measurement')))
351 if presets is not None:
352 ea.set_fields(presets)
353 if dlg.ShowModal() == wx.ID_OK:
354 dlg.DestroyLater()
355 return True
356 dlg.DestroyLater()
357 return False
358
359 #----------------------------------------------------------------
361
362 if parent is None:
363 parent = wx.GetApp().GetTopWindow()
364
365 if emr is None:
366 emr = gmPerson.gmCurrentPatient().emr
367
368 #------------------------------------------------------------
369 def edit(measurement=None):
370 return edit_measurement(parent = parent, measurement = measurement, single_entry = True)
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 # u'%s%s' % (
412 # gmTools.bool2subst (
413 # boolean = not r['reviewed'],
414 # true_return = _('no review at all'),
415 # false_return = gmTools.bool2subst (
416 # boolean = (r['you_are_responsible'] and not r['review_by_you']),
417 # true_return = _('no review by you (you are responsible)'),
418 # false_return = _('reviewed')
419 # )
420 # ),
421 # gmTools.coalesce(r['comment'], u'', u' / %s')
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 #================================================================
450
451 if parent is None:
452 parent = wx.GetApp().GetTopWindow()
453
454 panels = gmPathLab.get_test_panels(order_by = 'description')
455 gmCfgWidgets.configure_string_from_list_option (
456 parent = parent,
457 message = _('Select the measurements panel to show in the top pane for continuous monitoring.'),
458 option = 'horstspace.top_panel.lab_panel',
459 bias = 'user',
460 default_value = None,
461 choices = [ '%s%s' % (p['description'], gmTools.coalesce(p['comment'], '', ' (%s)')) for p in panels ],
462 columns = [_('Lab panel')],
463 data = [ p['pk_test_panel'] for p in panels ],
464 caption = _('Configuring continuous monitoring measurements panel')
465 )
466
467 #================================================================
469
470 from Gnumed.wxpython import gmFormWidgets
471
472 if parent is None:
473 parent = wx.GetApp().GetTopWindow()
474
475 template = gmFormWidgets.manage_form_templates (
476 parent = parent,
477 active_only = True,
478 template_types = ['gnuplot script']
479 )
480
481 option = 'form_templates.default_gnuplot_template'
482
483 if template is None:
484 gmDispatcher.send(signal = 'statustext', msg = _('No default Gnuplot script template selected.'), beep = True)
485 return None
486
487 if template['engine'] != 'G':
488 gmDispatcher.send(signal = 'statustext', msg = _('No default Gnuplot script template selected.'), beep = True)
489 return None
490
491 dbcfg = gmCfg.cCfgSQL()
492 dbcfg.set (
493 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
494 option = option,
495 value = '%s - %s' % (template['name_long'], template['external_version'])
496 )
497 return template
498
499 #============================================================
501
502 option = 'form_templates.default_gnuplot_template'
503
504 dbcfg = gmCfg.cCfgSQL()
505
506 # load from option
507 default_template_name = dbcfg.get2 (
508 option = option,
509 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
510 bias = 'user'
511 )
512
513 # not configured -> try to configure
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 # still not configured -> return
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 # now it MUST be configured (either newly or previously)
527 # but also *validly* ?
528 try:
529 name, ver = default_template_name.split(' - ')
530 except:
531 # not valid
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 # still not configured -> return
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):
551
552 from Gnumed.wxpython import gmFormWidgets
553
554 # only valid for one-type plotting
555 if use_default_template:
556 template = get_default_gnuplot_template()
557 else:
558 template = gmFormWidgets.manage_form_templates (
559 parent = parent,
560 active_only = True,
561 template_types = ['gnuplot script']
562 )
563
564 if template is None:
565 gmGuiHelpers.gm_show_error (
566 aMessage = _('Cannot plot without a plot script.'),
567 aTitle = _('Plotting test results')
568 )
569 return False
570
571 fname_data = gmPathLab.export_results_for_gnuplot(results = tests, show_year = show_year)
572
573 script = template.instantiate()
574 script.data_filename = fname_data
575 script.generate_output(format = format) # Gnuplot output terminal, wxt = wxWidgets window
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 #from Gnumed.wxGladeWidgets import wxgPrimaryCareVitalsInputPnl
600 #
601 # Taillenumfang: Mitte zwischen unterster Rippe und
602 # hoechstem Teil des Beckenkamms
603 # Maenner: maessig: 94-102, deutlich: > 102 .. erhoeht
604 # Frauen: maessig: 80-88, deutlich: > 88 .. erhoeht
605 #
606 #================================================================
607 # display widgets
608 #================================================================
609 from Gnumed.wxGladeWidgets import wxgLabRelatedDocumentsPnl
610
612 """This panel handles documents related to the lab result it is handed.
613 """
615 wxgLabRelatedDocumentsPnl.wxgLabRelatedDocumentsPnl.__init__(self, *args, **kwargs)
616
617 self.__reference = None
618
619 self.__init_ui()
620 self.__register_events()
621
622 #------------------------------------------------------------
623 # internal helpers
624 #------------------------------------------------------------
627
628 #------------------------------------------------------------
631
632 #------------------------------------------------------------
634 self._BTN_list_documents.Disable()
635 self._LBL_no_of_docs.SetLabel(_('no related documents'))
636 self._LBL_no_of_docs.ContainingSizer.Layout()
637
638 if self.__reference is None:
639 self._LBL_no_of_docs.SetToolTip(_('There is no lab reference to find related documents for.'))
640 return
641
642 dbcfg = gmCfg.cCfgSQL()
643 lab_doc_types = dbcfg.get2 (
644 option = 'horstspace.lab_doc_types',
645 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
646 bias = 'user'
647 )
648 if lab_doc_types is None:
649 self._LBL_no_of_docs.SetToolTip(_('No document types declared to contain lab results.'))
650 return
651
652 if len(lab_doc_types) == 0:
653 self._LBL_no_of_docs.SetToolTip(_('No document types declared to contain lab results.'))
654 return
655
656 pks_doc_types = gmDocuments.map_types2pk(lab_doc_types)
657 if len(pks_doc_types) == 0:
658 self._LBL_no_of_docs.SetToolTip(_('No valid document types declared to contain lab results.'))
659 return
660
661 txt = _('Document types assumed to contain lab results:')
662 txt += '\n '
663 txt += '\n '.join(lab_doc_types)
664 self._LBL_no_of_docs.SetToolTip(txt)
665 if isinstance(self.__reference, gmPathLab.cTestResult):
666 pk_current_episode = self.__reference['pk_episode']
667 else:
668 pk_current_episode = self.__reference
669 docs = gmDocuments.search_for_documents (
670 pk_episode = pk_current_episode,
671 pk_types = [ dt['pk_doc_type'] for dt in pks_doc_types ]
672 )
673 if len(docs) == 0:
674 return
675
676 self._LBL_no_of_docs.SetLabel(_('Related documents: %s') % len(docs))
677 self._LBL_no_of_docs.ContainingSizer.Layout()
678 self._BTN_list_documents.Enable()
679
680 #------------------------------------------------------------
681 # event handlers
682 #------------------------------------------------------------
684 if self.__reference is None:
685 return True
686
687 if kwds['table'] not in ['clin.test_result', 'blobs.doc_med']:
688 return True
689
690 if isinstance(self.__reference, gmPathLab.cTestResult):
691 if kwds['pk_of_row'] != self.__reference['pk_test_result']:
692 return True
693
694 self.__repopulate_ui()
695 return True
696
697 #------------------------------------------------------------
713
714 #------------------------------------------------------------
734
735 #------------------------------------------------------------
736 # properties
737 #------------------------------------------------------------
739 """Either a test result or an episode PK."""
740 if isinstance(self.__reference, gmPathLab.cTestResult):
741 pk_old_episode = self.__reference['pk_episode']
742 else:
743 pk_old_episode = self.__reference
744 if isinstance(value, gmPathLab.cTestResult):
745 pk_new_episode = value['pk_episode']
746 else:
747 pk_new_episode = value
748 self.__reference = value
749 if pk_new_episode != pk_old_episode:
750 self.__repopulate_ui()
751 return
752
753 lab_reference = property(lambda x:x, _set_lab_reference)
754
755 #================================================================
756 from Gnumed.wxGladeWidgets import wxgMeasurementsAsListPnl
757
758 -class cMeasurementsAsListPnl(wxgMeasurementsAsListPnl.wxgMeasurementsAsListPnl, gmRegetMixin.cRegetOnPaintMixin):
759 """A class for displaying all measurement results as a simple list.
760
761 - operates on a cPatient instance handed to it and NOT on the currently active patient
762 """
764 wxgMeasurementsAsListPnl.wxgMeasurementsAsListPnl.__init__(self, *args, **kwargs)
765
766 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
767
768 self.__patient = None
769
770 self.__init_ui()
771 self.__register_events()
772
773 #------------------------------------------------------------
774 # internal helpers
775 #------------------------------------------------------------
777 self._LCTRL_results.set_columns([_('When'), _('Test'), _('Result'), _('Reference')])
778 self._LCTRL_results.edit_callback = self._on_edit
779 self._PNL_related_documents.lab_reference = None
780
781 #------------------------------------------------------------
784
785 #------------------------------------------------------------
787 if self.__patient is None:
788 self._LCTRL_results.set_string_items([])
789 self._TCTRL_measurements.SetValue('')
790 self._PNL_related_documents.lab_reference = None
791 return
792
793 results = self.__patient.emr.get_test_results(order_by = 'clin_when DESC, unified_abbrev, unified_name')
794 items = []
795 data = []
796 for r in results:
797 range_info = gmTools.coalesce (
798 r.formatted_clinical_range,
799 r.formatted_normal_range
800 )
801 review = gmTools.bool2subst (
802 r['reviewed'],
803 '',
804 ' ' + gmTools.u_writing_hand,
805 ' ' + gmTools.u_writing_hand
806 )
807 items.append ([
808 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes),
809 r['abbrev_tt'],
810 '%s%s%s%s' % (
811 gmTools.strip_empty_lines(text = r['unified_val'])[0],
812 gmTools.coalesce(r['val_unit'], '', ' %s'),
813 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
814 review
815 ),
816 gmTools.coalesce(range_info, '')
817 ])
818 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
819
820 self._LCTRL_results.set_string_items(items)
821 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
822 self._LCTRL_results.set_data(data)
823 if len(items) > 0:
824 self._LCTRL_results.Select(idx = 0, on = 1)
825 self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted'])
826
827 self._LCTRL_results.SetFocus()
828
829 #------------------------------------------------------------
831 item_data = self._LCTRL_results.get_selected_item_data(only_one = True)
832 if item_data is None:
833 return
834 if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True):
835 self.__repopulate_ui()
836
837 #------------------------------------------------------------
838 # event handlers
839 #------------------------------------------------------------
841 if self.__patient is None:
842 return True
843
844 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet
845 if kwds['pk_identity'] != self.__patient.ID:
846 return True
847
848 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
849 return True
850
851 self._schedule_data_reget()
852 return True
853
854 #------------------------------------------------------------
856 event.Skip()
857 item_data = self._LCTRL_results.get_item_data(item_idx = event.Index)
858 self._TCTRL_measurements.SetValue(item_data['formatted'])
859 self._PNL_related_documents.lab_reference = item_data['data']
860
861 #------------------------------------------------------------
862 # reget mixin API
863 #------------------------------------------------------------
867
868 #------------------------------------------------------------
869 # properties
870 #------------------------------------------------------------
873
875 if (self.__patient is None) and (patient is None):
876 return
877 if (self.__patient is None) or (patient is None):
878 self.__patient = patient
879 self._schedule_data_reget()
880 return
881 if self.__patient.ID == patient.ID:
882 return
883 self.__patient = patient
884 self._schedule_data_reget()
885
886 patient = property(_get_patient, _set_patient)
887
888 #================================================================
889 from Gnumed.wxGladeWidgets import wxgMeasurementsByDayPnl
890
891 -class cMeasurementsByDayPnl(wxgMeasurementsByDayPnl.wxgMeasurementsByDayPnl, gmRegetMixin.cRegetOnPaintMixin):
892 """A class for displaying measurement results as a list partitioned by day.
893
894 - operates on a cPatient instance handed to it and NOT on the currently active patient
895 """
897 wxgMeasurementsByDayPnl.wxgMeasurementsByDayPnl.__init__(self, *args, **kwargs)
898
899 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
900
901 self.__patient = None
902 self.__date_format = str('%Y %b %d')
903
904 self.__init_ui()
905 self.__register_events()
906
907 #------------------------------------------------------------
908 # internal helpers
909 #------------------------------------------------------------
911 self._LCTRL_days.set_columns([_('Day')])
912 self._LCTRL_results.set_columns([_('Time'), _('Test'), _('Result'), _('Reference')])
913 self._LCTRL_results.edit_callback = self._on_edit
914 self._PNL_related_documents.lab_reference = None
915
916 #------------------------------------------------------------
919
920 #------------------------------------------------------------
922 self._LCTRL_days.set_string_items()
923 self._LCTRL_results.set_string_items()
924 self._TCTRL_measurements.SetValue('')
925 self._PNL_related_documents.lab_reference = None
926
927 #------------------------------------------------------------
929 if self.__patient is None:
930 self.__clear()
931 return
932
933 dates = self.__patient.emr.get_dates_for_results(reverse_chronological = True)
934 items = [ ['%s%s' % (
935 gmDateTime.pydt_strftime(d['clin_when_day'], self.__date_format),
936 gmTools.bool2subst(d['is_reviewed'], '', gmTools.u_writing_hand, gmTools.u_writing_hand)
937 )]
938 for d in dates
939 ]
940
941 self._LCTRL_days.set_string_items(items)
942 self._LCTRL_days.set_data(dates)
943 if len(items) > 0:
944 self._LCTRL_days.Select(idx = 0, on = 1)
945 self._LCTRL_days.SetFocus()
946
947 #------------------------------------------------------------
949 item_data = self._LCTRL_results.get_selected_item_data(only_one = True)
950 if item_data is None:
951 return
952 if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True):
953 self.__repopulate_ui()
954
955 #------------------------------------------------------------
956 # event handlers
957 #------------------------------------------------------------
959 if self.__patient is None:
960 return True
961
962 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet
963 if kwds['pk_identity'] != self.__patient.ID:
964 return True
965
966 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
967 return True
968
969 self._schedule_data_reget()
970 return True
971
972 #------------------------------------------------------------
974 event.Skip()
975
976 day = self._LCTRL_days.get_item_data(item_idx = event.Index)['clin_when_day']
977 results = self.__patient.emr.get_results_for_day(timestamp = day)
978 items = []
979 data = []
980 for r in results:
981 range_info = gmTools.coalesce (
982 r.formatted_clinical_range,
983 r.formatted_normal_range
984 )
985 review = gmTools.bool2subst (
986 r['reviewed'],
987 '',
988 ' ' + gmTools.u_writing_hand,
989 ' ' + gmTools.u_writing_hand
990 )
991 items.append ([
992 gmDateTime.pydt_strftime(r['clin_when'], '%H:%M'),
993 r['abbrev_tt'],
994 '%s%s%s%s' % (
995 gmTools.strip_empty_lines(text = r['unified_val'])[0],
996 gmTools.coalesce(r['val_unit'], '', ' %s'),
997 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
998 review
999 ),
1000 gmTools.coalesce(range_info, '')
1001 ])
1002 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
1003
1004 self._LCTRL_results.set_string_items(items)
1005 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1006 self._LCTRL_results.set_data(data)
1007 self._LCTRL_results.Select(idx = 0, on = 1)
1008
1009 #------------------------------------------------------------
1011 event.Skip()
1012 item_data = self._LCTRL_results.get_item_data(item_idx = event.Index)
1013 self._TCTRL_measurements.SetValue(item_data['formatted'])
1014 self._PNL_related_documents.lab_reference = item_data['data']
1015
1016 #------------------------------------------------------------
1017 # reget mixin API
1018 #------------------------------------------------------------
1022
1023 #------------------------------------------------------------
1024 # properties
1025 #------------------------------------------------------------
1028
1030 if (self.__patient is None) and (patient is None):
1031 return
1032 if patient is None:
1033 self.__patient = None
1034 self.__clear()
1035 return
1036 if self.__patient is None:
1037 self.__patient = patient
1038 self._schedule_data_reget()
1039 return
1040 if self.__patient.ID == patient.ID:
1041 return
1042 self.__patient = patient
1043 self._schedule_data_reget()
1044
1045 patient = property(_get_patient, _set_patient)
1046
1047 #================================================================
1048 from Gnumed.wxGladeWidgets import wxgMeasurementsByIssuePnl
1049
1050 -class cMeasurementsByIssuePnl(wxgMeasurementsByIssuePnl.wxgMeasurementsByIssuePnl, gmRegetMixin.cRegetOnPaintMixin):
1051 """A class for displaying measurement results as a list partitioned by issue/episode.
1052
1053 - operates on a cPatient instance handed to it and NOT on the currently active patient
1054 """
1056 wxgMeasurementsByIssuePnl.wxgMeasurementsByIssuePnl.__init__(self, *args, **kwargs)
1057
1058 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1059
1060 self.__patient = None
1061
1062 self.__init_ui()
1063 self.__register_events()
1064
1065 #------------------------------------------------------------
1066 # internal helpers
1067 #------------------------------------------------------------
1069 self._LCTRL_issues.set_columns([_('Problem')])
1070 self._LCTRL_results.set_columns([_('When'), _('Test'), _('Result'), _('Reference')])
1071 self._PNL_related_documents.lab_reference = None
1072
1073 #------------------------------------------------------------
1075 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
1076 self._LCTRL_issues.select_callback = self._on_problem_selected
1077 self._LCTRL_results.edit_callback = self._on_edit
1078 self._LCTRL_results.select_callback = self._on_result_selected
1079
1080 #------------------------------------------------------------
1082 self._LCTRL_issues.set_string_items()
1083 self._LCTRL_results.set_string_items()
1084 self._TCTRL_measurements.SetValue('')
1085 self._PNL_related_documents.lab_reference = None
1086
1087 #------------------------------------------------------------
1089 if self.__patient is None:
1090 self.__clear()
1091 return
1092
1093 probs = self.__patient.emr.get_issues_or_episodes_for_results()
1094 items = [ ['%s%s' % (
1095 gmTools.coalesce(p['pk_health_issue'], gmTools.u_diameter + ':', ''),
1096 gmTools.shorten_words_in_line(text = p['problem'], min_word_length = 5, max_length = 30)
1097 )] for p in probs ]
1098 self._LCTRL_issues.set_string_items(items)
1099 self._LCTRL_issues.set_data([ {'pk_issue': p['pk_health_issue'], 'pk_episode': p['pk_episode']} for p in probs ])
1100 if len(items) > 0:
1101 self._LCTRL_issues.Select(idx = 0, on = 1)
1102 self._LCTRL_issues.SetFocus()
1103
1104 #------------------------------------------------------------
1106 item_data = self._LCTRL_results.get_selected_item_data(only_one = True)
1107 if item_data is None:
1108 return
1109 if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True):
1110 self.__repopulate_ui()
1111
1112 #------------------------------------------------------------
1113 # event handlers
1114 #------------------------------------------------------------
1116 if self.__patient is None:
1117 return True
1118
1119 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet
1120 if kwds['pk_identity'] != self.__patient.ID:
1121 return True
1122
1123 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1124 return True
1125
1126 self._schedule_data_reget()
1127 return True
1128
1129 #------------------------------------------------------------
1131 event.Skip()
1132
1133 pk_issue = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_issue']
1134 if pk_issue is None:
1135 pk_episode = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_episode']
1136 results = self.__patient.emr.get_results_for_episode(pk_episode = pk_episode)
1137 else:
1138 results = self.__patient.emr.get_results_for_issue(pk_health_issue = pk_issue)
1139 items = []
1140 data = []
1141 for r in results:
1142 range_info = gmTools.coalesce (
1143 r.formatted_clinical_range,
1144 r.formatted_normal_range
1145 )
1146 review = gmTools.bool2subst (
1147 r['reviewed'],
1148 '',
1149 ' ' + gmTools.u_writing_hand,
1150 ' ' + gmTools.u_writing_hand
1151 )
1152 items.append ([
1153 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M'),
1154 r['abbrev_tt'],
1155 '%s%s%s%s' % (
1156 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1157 gmTools.coalesce(r['val_unit'], '', ' %s'),
1158 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1159 review
1160 ),
1161 gmTools.coalesce(range_info, '')
1162 ])
1163 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
1164
1165 self._LCTRL_results.set_string_items(items)
1166 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1167 self._LCTRL_results.set_data(data)
1168 self._LCTRL_results.Select(idx = 0, on = 1)
1169 self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted'])
1170
1171 #------------------------------------------------------------
1173 event.Skip()
1174 item_data = self._LCTRL_results.get_item_data(item_idx = event.Index)
1175 self._TCTRL_measurements.SetValue(item_data['formatted'])
1176 self._PNL_related_documents.lab_reference = item_data['data']
1177
1178 #------------------------------------------------------------
1179 # reget mixin API
1180 #------------------------------------------------------------
1184
1185 #------------------------------------------------------------
1186 # properties
1187 #------------------------------------------------------------
1190
1192 if (self.__patient is None) and (patient is None):
1193 return
1194 if patient is None:
1195 self.__patient = None
1196 self.__clear()
1197 return
1198 if self.__patient is None:
1199 self.__patient = patient
1200 self._schedule_data_reget()
1201 return
1202 if self.__patient.ID == patient.ID:
1203 return
1204 self.__patient = patient
1205 self._schedule_data_reget()
1206
1207 patient = property(_get_patient, _set_patient)
1208
1209 #================================================================
1210 from Gnumed.wxGladeWidgets import wxgMeasurementsByBatteryPnl
1211
1212 -class cMeasurementsByBatteryPnl(wxgMeasurementsByBatteryPnl.wxgMeasurementsByBatteryPnl, gmRegetMixin.cRegetOnPaintMixin):
1213 """A grid class for displaying measurement results filtered by battery/panel.
1214
1215 - operates on a cPatient instance handed to it and NOT on the currently active patient
1216 """
1218 wxgMeasurementsByBatteryPnl.wxgMeasurementsByBatteryPnl.__init__(self, *args, **kwargs)
1219
1220 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1221
1222 self.__patient = None
1223
1224 self.__init_ui()
1225 self.__register_events()
1226
1227 #------------------------------------------------------------
1228 # internal helpers
1229 #------------------------------------------------------------
1231 self._GRID_results_battery.show_by_panel = True
1232
1233 #------------------------------------------------------------
1235 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
1236
1237 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
1238 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
1239
1240 #------------------------------------------------------------
1244
1245 #--------------------------------------------------------
1247 if panel is None:
1248 self._TCTRL_panel_comment.SetValue('')
1249 self._GRID_results_battery.panel_to_show = None
1250 else:
1251 pnl = self._PRW_panel.GetData(as_instance = True)
1252 self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
1253 pnl['comment'],
1254 ''
1255 ))
1256 self._GRID_results_battery.panel_to_show = pnl
1257 # self.Layout()
1258
1259 #--------------------------------------------------------
1261 self._TCTRL_panel_comment.SetValue('')
1262 if self._PRW_panel.GetValue().strip() == '':
1263 self._GRID_results_battery.panel_to_show = None
1264 # self.Layout()
1265
1266 #------------------------------------------------------------
1267 # event handlers
1268 #------------------------------------------------------------
1270 if self.__patient is None:
1271 return True
1272
1273 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet
1274 if kwds['pk_identity'] != self.__patient.ID:
1275 return True
1276
1277 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1278 return True
1279
1280 self._schedule_data_reget()
1281 return True
1282
1283 #------------------------------------------------------------
1286
1287 #--------------------------------------------------------
1290
1291 #--------------------------------------------------------
1294
1295 #------------------------------------------------------------
1296 # reget mixin API
1297 #------------------------------------------------------------
1301
1302 #------------------------------------------------------------
1303 # properties
1304 #------------------------------------------------------------
1307
1309 if (self.__patient is None) and (patient is None):
1310 return
1311 if (self.__patient is None) or (patient is None):
1312 self.__patient = patient
1313 self._schedule_data_reget()
1314 return
1315 if self.__patient.ID == patient.ID:
1316 return
1317 self.__patient = patient
1318 self._schedule_data_reget()
1319
1320 patient = property(_get_patient, _set_patient)
1321
1322 #================================================================
1323 from Gnumed.wxGladeWidgets import wxgMeasurementsAsMostRecentListPnl
1324
1325 -class cMeasurementsAsMostRecentListPnl(wxgMeasurementsAsMostRecentListPnl.wxgMeasurementsAsMostRecentListPnl, gmRegetMixin.cRegetOnPaintMixin):
1326 """A list ctrl class for displaying measurement results.
1327
1328 - most recent results
1329 - possibly filtered by battery/panel
1330
1331 - operates on a cPatient instance handed to it and NOT on the currently active patient
1332 """
1334 wxgMeasurementsAsMostRecentListPnl.wxgMeasurementsAsMostRecentListPnl.__init__(self, *args, **kwargs)
1335
1336 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1337
1338 self.__patient = None
1339
1340 self.__init_ui()
1341 self.__register_events()
1342
1343 #------------------------------------------------------------
1344 # internal helpers
1345 #------------------------------------------------------------
1347 self._LCTRL_results.set_columns([_('Test'), _('Result'), _('When'), _('Range')])
1348 self._CHBOX_show_missing.Disable()
1349 self._PNL_related_documents.lab_reference = None
1350
1351 #------------------------------------------------------------
1353 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
1354
1355 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
1356 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
1357
1358 self._LCTRL_results.select_callback = self._on_result_selected
1359 self._LCTRL_results.edit_callback = self._on_edit
1360
1361 #------------------------------------------------------------
1363
1364 self._TCTRL_details.SetValue('')
1365 self._PNL_related_documents.lab_reference = None
1366
1367 pnl = self._PRW_panel.GetData(as_instance = True)
1368 if pnl is None:
1369 results = gmPathLab.get_most_recent_result_for_test_types (
1370 pk_patient = self.__patient.ID,
1371 consider_meta_type = True
1372 )
1373 else:
1374 results = pnl.get_most_recent_results (
1375 pk_patient = self.__patient.ID,
1376 #order_by = ,
1377 group_by_meta_type = True,
1378 include_missing = self._CHBOX_show_missing.IsChecked()
1379 )
1380 items = []
1381 data = []
1382 for r in results:
1383 if isinstance(r, gmPathLab.cTestResult):
1384 result_type = gmTools.coalesce (
1385 value2test = r['pk_meta_test_type'],
1386 return_instead = r['abbrev_tt'],
1387 value2return = '%s%s' % (gmTools.u_sum, r['abbrev_meta'])
1388 )
1389 review = gmTools.bool2subst (
1390 r['reviewed'],
1391 '',
1392 ' ' + gmTools.u_writing_hand,
1393 ' ' + gmTools.u_writing_hand
1394 )
1395 result_val = '%s%s%s%s' % (
1396 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1397 gmTools.coalesce(r['val_unit'], '', ' %s'),
1398 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1399 review
1400 )
1401 result_when = _('%s ago (%s)') % (
1402 gmDateTime.format_interval_medically(interval = gmDateTime.pydt_now_here() - r['clin_when']),
1403 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes)
1404 )
1405 range_info = gmTools.coalesce (
1406 r.formatted_clinical_range,
1407 r.formatted_normal_range
1408 )
1409 tt = r.format(with_source_data = True)
1410 else:
1411 result_type = r
1412 result_val = _('missing')
1413 loinc_data = gmLOINC.loinc2data(r)
1414 if loinc_data is None:
1415 result_when = _('LOINC not found')
1416 tt = u''
1417 else:
1418 result_when = loinc_data['term']
1419 tt = gmLOINC.format_loinc(r)
1420 range_info = None
1421 items.append([result_type, result_val, result_when, gmTools.coalesce(range_info, '')])
1422 data.append({'data': r, 'formatted': tt})
1423
1424 self._LCTRL_results.set_string_items(items)
1425 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1426 self._LCTRL_results.set_data(data)
1427
1428 if len(items) > 0:
1429 self._LCTRL_results.Select(idx = 0, on = 1)
1430 self._LCTRL_results.SetFocus()
1431
1432 return True
1433
1434 #--------------------------------------------------------
1436 if panel is None:
1437 self._TCTRL_panel_comment.SetValue('')
1438 self._CHBOX_show_missing.Disable()
1439 else:
1440 pnl = self._PRW_panel.GetData(as_instance = True)
1441 self._TCTRL_panel_comment.SetValue(gmTools.coalesce(pnl['comment'], ''))
1442 self.__repopulate_ui()
1443 self._CHBOX_show_missing.Enable()
1444
1445 #--------------------------------------------------------
1447 self._TCTRL_panel_comment.SetValue('')
1448 if self._PRW_panel.Value.strip() == u'':
1449 self.__repopulate_ui()
1450 self._CHBOX_show_missing.Disable()
1451
1452 #------------------------------------------------------------
1453 # event handlers
1454 #------------------------------------------------------------
1456 if self.__patient is None:
1457 return True
1458
1459 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet
1460 if kwds['pk_identity'] != self.__patient.ID:
1461 return True
1462
1463 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results', 'clin.test_panel']:
1464 return True
1465
1466 self._schedule_data_reget()
1467 return True
1468
1469 #------------------------------------------------------------
1472
1473 #--------------------------------------------------------
1476
1477 #--------------------------------------------------------
1480
1481 #------------------------------------------------------------
1483 event.Skip()
1484 item_data = self._LCTRL_results.get_item_data(item_idx = event.Index)
1485 self._TCTRL_details.SetValue(item_data['formatted'])
1486 if isinstance(item_data['data'], gmPathLab.cTestResult):
1487 self._PNL_related_documents.lab_reference = item_data['data']
1488 else:
1489 self._PNL_related_documents.lab_reference = None
1490
1491 #------------------------------------------------------------
1493 item_data = self._LCTRL_results.get_selected_item_data(only_one = True)
1494 if item_data is None:
1495 return
1496 if isinstance(item_data['data'], gmPathLab.cTestResult):
1497 if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True):
1498 self.__repopulate_ui()
1499
1500 #------------------------------------------------------------
1502 event.Skip()
1503 # should not happen
1504 if self._PRW_panel.GetData(as_instance = False) is None:
1505 return
1506 self.__repopulate_ui()
1507
1508 #------------------------------------------------------------
1509 # reget mixin API
1510 #------------------------------------------------------------
1514
1515 #------------------------------------------------------------
1516 # properties
1517 #------------------------------------------------------------
1520
1522 if (self.__patient is None) and (patient is None):
1523 return
1524 if (self.__patient is None) or (patient is None):
1525 self.__patient = patient
1526 self._schedule_data_reget()
1527 return
1528 if self.__patient.ID == patient.ID:
1529 return
1530 self.__patient = patient
1531 self._schedule_data_reget()
1532
1533 patient = property(_get_patient, _set_patient)
1534
1535 #================================================================
1536 from Gnumed.wxGladeWidgets import wxgMeasurementsAsTablePnl
1537
1538 -class cMeasurementsAsTablePnl(wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl, gmRegetMixin.cRegetOnPaintMixin):
1539 """A panel for holding a grid displaying all measurement results.
1540
1541 - operates on a cPatient instance handed to it and NOT on the currently active patient
1542 """
1544 wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl.__init__(self, *args, **kwargs)
1545
1546 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1547
1548 self.__patient = None
1549
1550 self.__init_ui()
1551 self.__register_events()
1552
1553 #------------------------------------------------------------
1554 # internal helpers
1555 #------------------------------------------------------------
1557 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
1558
1559 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
1560 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
1561
1562 item = self.__action_button_popup.Append(-1, _('Plot'))
1563 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
1564
1565 #item = self.__action_button_popup.Append(-1, _('Export to &file'))
1566 #self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item)
1567 #self.__action_button_popup.Enable(id = item.Id, enable = False)
1568
1569 #item = self.__action_button_popup.Append(-1, _('Export to &clipboard'))
1570 #self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item)
1571 #self.__action_button_popup.Enable(id = item.Id, enable = False)
1572
1573 item = self.__action_button_popup.Append(-1, _('&Delete'))
1574 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
1575
1576 # FIXME: create inbox message to staff to phone patient to come in
1577 # FIXME: generate and let edit a SOAP narrative and include the values
1578
1579 self._GRID_results_all.show_by_panel = False
1580
1581 #------------------------------------------------------------
1584
1585 #------------------------------------------------------------
1587 self._GRID_results_all.patient = self.__patient
1588 #self._GRID_results_battery.Fit()
1589 self.Layout()
1590 return True
1591
1592 #------------------------------------------------------------
1594 self._GRID_results_all.sign_current_selection()
1595
1596 #------------------------------------------------------------
1598 self._GRID_results_all.plot_current_selection()
1599
1600 #------------------------------------------------------------
1602 self._GRID_results_all.delete_current_selection()
1603
1604 #------------------------------------------------------------
1605 # event handlers
1606 #------------------------------------------------------------
1608 if self.__patient is None:
1609 return True
1610
1611 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet
1612 if kwds['pk_identity'] != self.__patient.ID:
1613 return True
1614
1615 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1616 return True
1617
1618 self._schedule_data_reget()
1619 return True
1620
1621 #--------------------------------------------------------
1624
1625 #--------------------------------------------------------
1629
1630 #--------------------------------------------------------
1633
1634 #--------------------------------------------------------
1640
1641 #------------------------------------------------------------
1642 # reget mixin API
1643 #------------------------------------------------------------
1647
1648 #------------------------------------------------------------
1649 # properties
1650 #------------------------------------------------------------
1653
1655 if (self.__patient is None) and (patient is None):
1656 return
1657 if (self.__patient is None) or (patient is None):
1658 self.__patient = patient
1659 self._schedule_data_reget()
1660 return
1661 if self.__patient.ID == patient.ID:
1662 return
1663 self.__patient = patient
1664 self._schedule_data_reget()
1665
1666 patient = property(_get_patient, _set_patient)
1667
1668 #================================================================
1669 # notebook based measurements plugin
1670 #================================================================
1672 """Notebook displaying measurements pages:
1673
1674 - by test battery
1675 - by day
1676 - by issue/episode
1677 - most-recent list, perhaps by panel
1678 - full grid
1679 - full list
1680
1681 Used as a main notebook plugin page.
1682
1683 Operates on the active patient.
1684 """
1685 #--------------------------------------------------------
1687
1688 wx.Notebook.__init__ (
1689 self,
1690 parent = parent,
1691 id = id,
1692 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
1693 name = self.__class__.__name__
1694 )
1695 _log.debug('created wx.Notebook: %s with ID %s', self.__class__.__name__, self.Id)
1696 gmPlugin.cPatientChange_PluginMixin.__init__(self)
1697 self.__patient = gmPerson.gmCurrentPatient()
1698 self.__init_ui()
1699 self.SetSelection(0)
1700
1701 #--------------------------------------------------------
1702 # patient change plugin API
1703 #--------------------------------------------------------
1705 for page_idx in range(self.GetPageCount()):
1706 page = self.GetPage(page_idx)
1707 page.patient = None
1708
1709 #--------------------------------------------------------
1711 for page_idx in range(self.GetPageCount()):
1712 page = self.GetPage(page_idx)
1713 page.patient = self.__patient.patient
1714
1715 #--------------------------------------------------------
1716 # notebook plugin API
1717 #--------------------------------------------------------
1719 if self.__patient.connected:
1720 pat = self.__patient.patient
1721 else:
1722 pat = None
1723 for page_idx in range(self.GetPageCount()):
1724 page = self.GetPage(page_idx)
1725 page.patient = pat
1726
1727 return True
1728
1729 #--------------------------------------------------------
1730 # internal API
1731 #--------------------------------------------------------
1733
1734 # by day
1735 new_page = cMeasurementsByDayPnl(self, -1)
1736 new_page.patient = None
1737 self.AddPage (
1738 page = new_page,
1739 text = _('Days'),
1740 select = True
1741 )
1742
1743 # by issue
1744 new_page = cMeasurementsByIssuePnl(self, -1)
1745 new_page.patient = None
1746 self.AddPage (
1747 page = new_page,
1748 text = _('Problems'),
1749 select = False
1750 )
1751
1752 # by test panel
1753 new_page = cMeasurementsByBatteryPnl(self, -1)
1754 new_page.patient = None
1755 self.AddPage (
1756 page = new_page,
1757 text = _('Panels'),
1758 select = False
1759 )
1760
1761 # most-recent, by panel
1762 new_page = cMeasurementsAsMostRecentListPnl(self, -1)
1763 new_page.patient = None
1764 self.AddPage (
1765 page = new_page,
1766 text = _('Most recent'),
1767 select = False
1768 )
1769
1770 # full grid
1771 new_page = cMeasurementsAsTablePnl(self, -1)
1772 new_page.patient = None
1773 self.AddPage (
1774 page = new_page,
1775 text = _('Table'),
1776 select = False
1777 )
1778
1779 # full list
1780 new_page = cMeasurementsAsListPnl(self, -1)
1781 new_page.patient = None
1782 self.AddPage (
1783 page = new_page,
1784 text = _('List'),
1785 select = False
1786 )
1787
1788 #--------------------------------------------------------
1789 # properties
1790 #--------------------------------------------------------
1793
1795 self.__patient = patient
1796 if self.__patient.connected:
1797 pat = self.__patient.patient
1798 else:
1799 pat = None
1800 for page_idx in range(self.GetPageCount()):
1801 page = self.GetPage(page_idx)
1802 page.patient = pat
1803
1804 patient = property(_get_patient, _set_patient)
1805
1806 #================================================================
1808 """A grid class for displaying measurement results.
1809
1810 - operates on a cPatient instance handed to it
1811 - does NOT listen to the currently active patient
1812 - thereby it can display any patient at any time
1813 """
1814 # FIXME: sort-by-battery
1815 # FIXME: filter out empty
1816 # FIXME: filter by tests of a selected date
1817 # FIXME: dates DESC/ASC by cfg
1818 # FIXME: mouse over column header: display date info
1820
1821 wx.grid.Grid.__init__(self, *args, **kwargs)
1822
1823 self.__patient = None
1824 self.__panel_to_show = None
1825 self.__show_by_panel = False
1826 self.__cell_data = {}
1827 self.__row_label_data = []
1828 self.__col_label_data = []
1829
1830 self.__prev_row = None
1831 self.__prev_col = None
1832 self.__prev_label_row = None
1833 self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::'))
1834
1835 self.__init_ui()
1836 self.__register_events()
1837
1838 #------------------------------------------------------------
1839 # external API
1840 #------------------------------------------------------------
1842 if not self.IsSelection():
1843 gmDispatcher.send(signal = 'statustext', msg = _('No results selected for deletion.'))
1844 return True
1845
1846 selected_cells = self.get_selected_cells()
1847 if len(selected_cells) > 20:
1848 results = None
1849 msg = _(
1850 'There are %s results marked for deletion.\n'
1851 '\n'
1852 'Are you sure you want to delete these results ?'
1853 ) % len(selected_cells)
1854 else:
1855 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1856 txt = '\n'.join([ '%s %s (%s): %s %s%s' % (
1857 r['clin_when'].strftime('%x %H:%M'),
1858 r['unified_abbrev'],
1859 r['unified_name'],
1860 r['unified_val'],
1861 r['val_unit'],
1862 gmTools.coalesce(r['abnormality_indicator'], '', ' (%s)')
1863 ) for r in results
1864 ])
1865 msg = _(
1866 'The following results are marked for deletion:\n'
1867 '\n'
1868 '%s\n'
1869 '\n'
1870 'Are you sure you want to delete these results ?'
1871 ) % txt
1872
1873 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
1874 self,
1875 -1,
1876 caption = _('Deleting test results'),
1877 question = msg,
1878 button_defs = [
1879 {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False},
1880 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True}
1881 ]
1882 )
1883 decision = dlg.ShowModal()
1884
1885 if decision == wx.ID_YES:
1886 if results is None:
1887 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1888 for result in results:
1889 gmPathLab.delete_test_result(result)
1890
1891 #------------------------------------------------------------
1893 if not self.IsSelection():
1894 gmDispatcher.send(signal = 'statustext', msg = _('Cannot sign results. No results selected.'))
1895 return True
1896
1897 selected_cells = self.get_selected_cells()
1898 tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1899
1900 return review_tests(parent = self, tests = tests)
1901
1902 #------------------------------------------------------------
1904
1905 if not self.IsSelection():
1906 gmDispatcher.send(signal = 'statustext', msg = _('Cannot plot results. No results selected.'))
1907 return True
1908
1909 tests = self.__cells_to_data (
1910 cells = self.get_selected_cells(),
1911 exclude_multi_cells = False,
1912 auto_include_multi_cells = True
1913 )
1914
1915 plot_measurements(parent = self, tests = tests)
1916 #------------------------------------------------------------
1918
1919 sel_block_top_left = self.GetSelectionBlockTopLeft()
1920 sel_block_bottom_right = self.GetSelectionBlockBottomRight()
1921 sel_cols = self.GetSelectedCols()
1922 sel_rows = self.GetSelectedRows()
1923
1924 selected_cells = []
1925
1926 # individually selected cells (ctrl-click)
1927 selected_cells += self.GetSelectedCells()
1928
1929 # selected rows
1930 selected_cells += list (
1931 (row, col)
1932 for row in sel_rows
1933 for col in range(self.GetNumberCols())
1934 )
1935
1936 # selected columns
1937 selected_cells += list (
1938 (row, col)
1939 for row in range(self.GetNumberRows())
1940 for col in sel_cols
1941 )
1942
1943 # selection blocks
1944 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()):
1945 selected_cells += [
1946 (row, col)
1947 for row in range(top_left[0], bottom_right[0] + 1)
1948 for col in range(top_left[1], bottom_right[1] + 1)
1949 ]
1950
1951 return set(selected_cells)
1952 #------------------------------------------------------------
1953 - def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
1954 """Select a range of cells according to criteria.
1955
1956 unsigned_only: include only those which are not signed at all yet
1957 accountable_only: include only those for which the current user is responsible
1958 keep_preselections: broaden (rather than replace) the range of selected cells
1959
1960 Combinations are powerful !
1961 """
1962 wx.BeginBusyCursor()
1963 self.BeginBatch()
1964
1965 if not keep_preselections:
1966 self.ClearSelection()
1967
1968 for col_idx in self.__cell_data.keys():
1969 for row_idx in self.__cell_data[col_idx].keys():
1970 # loop over results in cell and only include
1971 # those multi-value cells that are not ambiguous
1972 do_not_include = False
1973 for result in self.__cell_data[col_idx][row_idx]:
1974 if unsigned_only:
1975 if result['reviewed']:
1976 do_not_include = True
1977 break
1978 if accountables_only:
1979 if not result['you_are_responsible']:
1980 do_not_include = True
1981 break
1982 if do_not_include:
1983 continue
1984
1985 self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True)
1986
1987 self.EndBatch()
1988 wx.EndBusyCursor()
1989
1990 #------------------------------------------------------------
1992 self.empty_grid()
1993 if self.__patient is None:
1994 return
1995
1996 if self.__show_by_panel:
1997 if self.__panel_to_show is None:
1998 return
1999 tests = self.__panel_to_show.get_test_types_for_results (
2000 self.__patient.ID,
2001 order_by = 'unified_abbrev',
2002 unique_meta_types = True
2003 )
2004 self.__repopulate_grid (
2005 tests4rows = tests,
2006 test_pks2show = [ tt['pk_test_type'] for tt in self.__panel_to_show['test_types'] ]
2007 )
2008 return
2009
2010 emr = self.__patient.emr
2011 tests = emr.get_test_types_for_results(order_by = 'unified_abbrev', unique_meta_types = True)
2012 self.__repopulate_grid(tests4rows = tests)
2013
2014 #------------------------------------------------------------
2016
2017 if len(tests4rows) == 0:
2018 return
2019
2020 emr = self.__patient.emr
2021
2022 self.__row_label_data = tests4rows
2023 row_labels = [ '%s%s' % (
2024 gmTools.bool2subst(test_type['is_fake_meta_type'], '', gmTools.u_sum, ''),
2025 test_type['unified_abbrev']
2026 ) for test_type in self.__row_label_data
2027 ]
2028
2029 self.__col_label_data = [ d['clin_when_day'] for d in emr.get_dates_for_results (
2030 tests = test_pks2show,
2031 reverse_chronological = True
2032 )]
2033 col_labels = [ gmDateTime.pydt_strftime(date, self.__date_format, accuracy = gmDateTime.acc_days) for date in self.__col_label_data ]
2034
2035 results = emr.get_test_results_by_date (
2036 tests = test_pks2show,
2037 reverse_chronological = True
2038 )
2039
2040 self.BeginBatch()
2041
2042 # rows
2043 self.AppendRows(numRows = len(row_labels))
2044 for row_idx in range(len(row_labels)):
2045 self.SetRowLabelValue(row_idx, row_labels[row_idx])
2046
2047 # columns
2048 self.AppendCols(numCols = len(col_labels))
2049 for col_idx in range(len(col_labels)):
2050 self.SetColLabelValue(col_idx, col_labels[col_idx])
2051
2052 # cell values (list of test results)
2053 for result in results:
2054 row_idx = row_labels.index('%s%s' % (
2055 gmTools.bool2subst(result['is_fake_meta_type'], '', gmTools.u_sum, ''),
2056 result['unified_abbrev']
2057 ))
2058 col_idx = col_labels.index(gmDateTime.pydt_strftime(result['clin_when'], self.__date_format, accuracy = gmDateTime.acc_days))
2059
2060 try:
2061 self.__cell_data[col_idx]
2062 except KeyError:
2063 self.__cell_data[col_idx] = {}
2064
2065 # the tooltip always shows the youngest sub result details
2066 if row_idx in self.__cell_data[col_idx]:
2067 self.__cell_data[col_idx][row_idx].append(result)
2068 self.__cell_data[col_idx][row_idx].sort(key = lambda x: x['clin_when'], reverse = True)
2069 else:
2070 self.__cell_data[col_idx][row_idx] = [result]
2071
2072 # rebuild cell display string
2073 vals2display = []
2074 cell_has_out_of_bounds_value = False
2075 for sub_result in self.__cell_data[col_idx][row_idx]:
2076
2077 if sub_result.is_considered_abnormal:
2078 cell_has_out_of_bounds_value = True
2079
2080 abnormality_indicator = sub_result.formatted_abnormality_indicator
2081 if abnormality_indicator is None:
2082 abnormality_indicator = ''
2083 if abnormality_indicator != '':
2084 abnormality_indicator = ' (%s)' % abnormality_indicator[:3]
2085
2086 missing_review = False
2087 # warn on missing review if
2088 # a) no review at all exists or
2089 if not sub_result['reviewed']:
2090 missing_review = True
2091 # b) there is a review but
2092 else:
2093 # current user is reviewer and hasn't reviewed
2094 if sub_result['you_are_responsible'] and not sub_result['review_by_you']:
2095 missing_review = True
2096
2097 needs_superscript = False
2098
2099 # can we display the full sub_result length ?
2100 if sub_result.is_long_text:
2101 lines = gmTools.strip_empty_lines (
2102 text = sub_result['unified_val'],
2103 eol = '\n',
2104 return_list = True
2105 )
2106 needs_superscript = True
2107 tmp = lines[0][:7]
2108 else:
2109 val = gmTools.strip_empty_lines (
2110 text = sub_result['unified_val'],
2111 eol = '\n',
2112 return_list = False
2113 ).replace('\n', '//')
2114 if len(val) > 8:
2115 needs_superscript = True
2116 tmp = val[:7]
2117 else:
2118 tmp = '%.8s' % val[:8]
2119
2120 # abnormal ?
2121 tmp = '%s%.6s' % (tmp, abnormality_indicator)
2122
2123 # is there a comment ?
2124 has_sub_result_comment = gmTools.coalesce (
2125 gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']),
2126 ''
2127 ).strip() != ''
2128 if has_sub_result_comment:
2129 needs_superscript = True
2130
2131 if needs_superscript:
2132 tmp = '%s%s' % (tmp, gmTools.u_superscript_one)
2133
2134 # lacking a review ?
2135 if missing_review:
2136 tmp = '%s %s' % (tmp, gmTools.u_writing_hand)
2137 else:
2138 if sub_result['is_clinically_relevant']:
2139 tmp += ' !'
2140
2141 # part of a multi-result cell ?
2142 if len(self.__cell_data[col_idx][row_idx]) > 1:
2143 tmp = '%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp)
2144
2145 vals2display.append(tmp)
2146
2147 self.SetCellValue(row_idx, col_idx, '\n'.join(vals2display))
2148 self.SetCellAlignment(row_idx, col_idx, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
2149 # We used to color text in cells holding abnormals
2150 # in firebrick red but that would color ALL text (including
2151 # normals) and not only the abnormals within that
2152 # cell. Shading, however, only says that *something*
2153 # inside that cell is worthy of attention.
2154 #if sub_result_relevant:
2155 # font = self.GetCellFont(row_idx, col_idx)
2156 # self.SetCellTextColour(row_idx, col_idx, 'firebrick')
2157 # font.SetWeight(wx.FONTWEIGHT_BOLD)
2158 # self.SetCellFont(row_idx, col_idx, font)
2159 if cell_has_out_of_bounds_value:
2160 #self.SetCellBackgroundColour(row_idx, col_idx, 'cornflower blue')
2161 self.SetCellBackgroundColour(row_idx, col_idx, 'PALE TURQUOISE')
2162
2163 self.EndBatch()
2164
2165 self.AutoSize()
2166 self.AdjustScrollbars()
2167 self.ForceRefresh()
2168
2169 #self.Fit()
2170
2171 return
2172
2173 #------------------------------------------------------------
2175 self.BeginBatch()
2176 self.ClearGrid()
2177 # Windows cannot do nothing, it rather decides to assert()
2178 # on thinking it is supposed to do nothing
2179 if self.GetNumberRows() > 0:
2180 self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
2181 if self.GetNumberCols() > 0:
2182 self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
2183 self.EndBatch()
2184 self.__cell_data = {}
2185 self.__row_label_data = []
2186 self.__col_label_data = []
2187
2188 #------------------------------------------------------------
2190 # include details about test types included ?
2191
2192 # sometimes, for some reason, there is no row and
2193 # wxPython still tries to find a tooltip for it
2194 try:
2195 tt = self.__row_label_data[row]
2196 except IndexError:
2197 return ' '
2198
2199 if tt['is_fake_meta_type']:
2200 return tt.format(patient = self.__patient.ID)
2201
2202 meta_tt = tt.meta_test_type
2203 txt = meta_tt.format(with_tests = True, patient = self.__patient.ID)
2204
2205 return txt
2206
2207 #------------------------------------------------------------
2209 try:
2210 cell_results = self.__cell_data[col][row]
2211 except KeyError:
2212 # FIXME: maybe display the most recent or when the most recent was ?
2213 cell_results = None
2214
2215 if cell_results is None:
2216 return ' '
2217
2218 is_multi_cell = False
2219 if len(cell_results) > 1:
2220 is_multi_cell = True
2221 result = cell_results[0]
2222
2223 tt = ''
2224 # header
2225 if is_multi_cell:
2226 tt += _('Details of most recent (topmost) result ! \n')
2227 if result.is_long_text:
2228 tt += gmTools.strip_empty_lines(text = result['val_alpha'], eol = '\n', return_list = False)
2229 return tt
2230
2231 tt += result.format(with_review = True, with_evaluation = True, with_ranges = True)
2232 return tt
2233
2234 #------------------------------------------------------------
2235 # internal helpers
2236 #------------------------------------------------------------
2238 #self.SetMinSize(wx.DefaultSize)
2239 self.SetMinSize((10, 10))
2240
2241 self.CreateGrid(0, 1)
2242 self.EnableEditing(0)
2243 self.EnableDragGridSize(1)
2244
2245 # column labels
2246 # setting this screws up the labels: they are cut off and displaced
2247 #self.SetColLabelAlignment(wx.ALIGN_CENTER, wx.ALIGN_BOTTOM)
2248
2249 # row labels
2250 self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE) # starting with 2.8.8
2251 #self.SetRowLabelSize(150)
2252 self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE)
2253 font = self.GetLabelFont()
2254 font.SetWeight(wx.FONTWEIGHT_LIGHT)
2255 self.SetLabelFont(font)
2256
2257 # add link to left upper corner
2258 dbcfg = gmCfg.cCfgSQL()
2259 url = dbcfg.get2 (
2260 option = 'external.urls.measurements_encyclopedia',
2261 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
2262 bias = 'user',
2263 default = gmPathLab.URL_test_result_information
2264 )
2265
2266 self.__WIN_corner = self.GetGridCornerLabelWindow() # a wx.Window instance
2267
2268 LNK_lab = wxh.HyperlinkCtrl (
2269 self.__WIN_corner,
2270 -1,
2271 label = _('Tests'),
2272 style = wxh.HL_DEFAULT_STYLE # wx.TE_READONLY|wx.TE_CENTRE| wx.NO_BORDER |
2273 )
2274 LNK_lab.SetURL(url)
2275 LNK_lab.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND))
2276 LNK_lab.SetToolTip(_(
2277 'Navigate to an encyclopedia of measurements\n'
2278 'and test methods on the web.\n'
2279 '\n'
2280 ' <%s>'
2281 ) % url)
2282
2283 SZR_inner = wx.BoxSizer(wx.HORIZONTAL)
2284 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer
2285 SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0) #wx.ALIGN_CENTER wx.EXPAND
2286 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer
2287
2288 SZR_corner = wx.BoxSizer(wx.VERTICAL)
2289 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer
2290 SZR_corner.Add(SZR_inner, 0, wx.EXPAND) # inner sizer with centered hyperlink
2291 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer
2292
2293 self.__WIN_corner.SetSizer(SZR_corner)
2294 SZR_corner.Fit(self.__WIN_corner)
2295
2296 #------------------------------------------------------------
2299
2300 #------------------------------------------------------------
2301 - def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
2302 """List of <cells> must be in row / col order."""
2303 data = []
2304 for row, col in cells:
2305 try:
2306 # cell data is stored col / row
2307 data_list = self.__cell_data[col][row]
2308 except KeyError:
2309 continue
2310
2311 if len(data_list) == 1:
2312 data.append(data_list[0])
2313 continue
2314
2315 if exclude_multi_cells:
2316 gmDispatcher.send(signal = 'statustext', msg = _('Excluding multi-result field from further processing.'))
2317 continue
2318
2319 if auto_include_multi_cells:
2320 data.extend(data_list)
2321 continue
2322
2323 data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list)
2324 if data_to_include is None:
2325 continue
2326 data.extend(data_to_include)
2327
2328 return data
2329
2330 #------------------------------------------------------------
2332 data = gmListWidgets.get_choices_from_list (
2333 parent = self,
2334 msg = _(
2335 'Your selection includes a field with multiple results.\n'
2336 '\n'
2337 'Please select the individual results you want to work on:'
2338 ),
2339 caption = _('Selecting test results'),
2340 choices = [ [d['clin_when'], '%s: %s' % (d['abbrev_tt'], d['name_tt']), d['unified_val']] for d in cell_data ],
2341 columns = [ _('Date / Time'), _('Test'), _('Result') ],
2342 data = cell_data,
2343 single_selection = single_selection
2344 )
2345 return data
2346
2347 #------------------------------------------------------------
2348 # event handling
2349 #------------------------------------------------------------
2351 # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow
2352 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
2353 self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels)
2354 #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels)
2355
2356 # sizing left upper corner window
2357 self.Bind(wx.EVT_SIZE, self.__resize_corner_window)
2358
2359 # editing cells
2360 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
2361
2362 #------------------------------------------------------------
2364 col = evt.GetCol()
2365 row = evt.GetRow()
2366
2367 try:
2368 self.__cell_data[col][row]
2369 except KeyError: # empty cell
2370 presets = {}
2371 col_date = self.__col_label_data[col]
2372 presets['clin_when'] = {'data': col_date}
2373 test_type = self.__row_label_data[row]
2374 temporally_closest_result_of_row_type = test_type.meta_test_type.get_temporally_closest_result(col_date, self.__patient.ID)
2375 if temporally_closest_result_of_row_type is not None:
2376 presets['pk_test_type'] = {'data': temporally_closest_result_of_row_type['pk_test_type']}
2377 same_day_results = gmPathLab.get_results_for_day (
2378 timestamp = col_date,
2379 patient = self.__patient.ID,
2380 order_by = None
2381 )
2382 if len(same_day_results) > 0:
2383 presets['pk_episode'] = {'data': same_day_results[0]['pk_episode']}
2384 # maybe ['comment'] as in "medical context" ? - not thought through yet
2385 # no need to set because because setting pk_test_type will do so:
2386 # presets['val_unit']
2387 # presets['val_normal_min']
2388 # presets['val_normal_max']
2389 # presets['val_normal_range']
2390 # presets['val_target_min']
2391 # presets['val_target_max']
2392 # presets['val_target_range']
2393 edit_measurement (
2394 parent = self,
2395 measurement = None,
2396 single_entry = True,
2397 presets = presets
2398 )
2399 return
2400
2401 if len(self.__cell_data[col][row]) > 1:
2402 data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True)
2403 else:
2404 data = self.__cell_data[col][row][0]
2405
2406 if data is None:
2407 return
2408
2409 edit_measurement(parent = self, measurement = data, single_entry = True)
2410
2411 #------------------------------------------------------------
2412 # def OnMouseMotionRowLabel(self, evt):
2413 # x, y = self.CalcUnscrolledPosition(evt.GetPosition())
2414 # row = self.YToRow(y)
2415 # label = self.table().GetRowHelpValue(row)
2416 # self.GetGridRowLabelWindow().SetToolTip(label or "")
2417 # evt.Skip()
2419
2420 # Use CalcUnscrolledPosition() to get the mouse position within the
2421 # entire grid including what's offscreen
2422 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2423
2424 row = self.YToRow(y)
2425
2426 if self.__prev_label_row == row:
2427 return
2428
2429 self.__prev_label_row == row
2430
2431 evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row))
2432 #------------------------------------------------------------
2433 # def OnMouseMotionColLabel(self, evt):
2434 # x, y = self.CalcUnscrolledPosition(evt.GetPosition())
2435 # col = self.XToCol(x)
2436 # label = self.table().GetColHelpValue(col)
2437 # self.GetGridColLabelWindow().SetToolTip(label or "")
2438 # evt.Skip()
2439 #------------------------------------------------------------
2441 """Calculate where the mouse is and set the tooltip dynamically."""
2442
2443 # Use CalcUnscrolledPosition() to get the mouse position within the
2444 # entire grid including what's offscreen
2445 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2446
2447 # use this logic to prevent tooltips outside the actual cells
2448 # apply to GetRowSize, too
2449 # tot = 0
2450 # for col in range(self.NumberCols):
2451 # tot += self.GetColSize(col)
2452 # if xpos <= tot:
2453 # self.tool_tip.Tip = 'Tool tip for Column %s' % (
2454 # self.GetColLabelValue(col))
2455 # break
2456 # else: # mouse is in label area beyond the right-most column
2457 # self.tool_tip.Tip = ''
2458
2459 row, col = self.XYToCell(x, y)
2460
2461 if (row == self.__prev_row) and (col == self.__prev_col):
2462 return
2463
2464 self.__prev_row = row
2465 self.__prev_col = col
2466
2467 evt.GetEventObject().SetToolTip(self.get_cell_tooltip(col=col, row=row))
2468
2469 #------------------------------------------------------------
2470 # properties
2471 #------------------------------------------------------------
2474
2478
2479 patient = property(_get_patient, _set_patient)
2480 #------------------------------------------------------------
2484
2485 panel_to_show = property(lambda x:x, _set_panel_to_show)
2486 #------------------------------------------------------------
2490
2491 show_by_panel = property(lambda x:x, _set_show_by_panel)
2492
2493 #================================================================
2494 # integrated measurements plugin
2495 #================================================================
2496 from Gnumed.wxGladeWidgets import wxgMeasurementsPnl
2497
2498 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
2499 """Panel holding a grid with lab data. Used as notebook page."""
2500
2502
2503 wxgMeasurementsPnl.wxgMeasurementsPnl.__init__(self, *args, **kwargs)
2504 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
2505 self.__display_mode = 'grid'
2506 self.__init_ui()
2507 self.__register_interests()
2508 #--------------------------------------------------------
2509 # event handling
2510 #--------------------------------------------------------
2512 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
2513 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
2514 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._schedule_data_reget)
2515 gmDispatcher.connect(signal = 'clin.reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
2516 #--------------------------------------------------------
2519 #--------------------------------------------------------
2523 #--------------------------------------------------------
2526 #--------------------------------------------------------
2530 #--------------------------------------------------------
2534 #--------------------------------------------------------
2537 #--------------------------------------------------------
2543 #--------------------------------------------------------
2546 #--------------------------------------------------------
2566 #--------------------------------------------------------
2568 self._GRID_results_all.sign_current_selection()
2569 #--------------------------------------------------------
2571 self._GRID_results_all.plot_current_selection()
2572 #--------------------------------------------------------
2574 self._GRID_results_all.delete_current_selection()
2575 #--------------------------------------------------------
2578 #--------------------------------------------------------
2580 if panel is None:
2581 self._TCTRL_panel_comment.SetValue('')
2582 self._GRID_results_battery.panel_to_show = None
2583 #self._GRID_results_battery.Hide()
2584 self._PNL_results_battery_grid.Hide()
2585 else:
2586 pnl = self._PRW_panel.GetData(as_instance = True)
2587 self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
2588 pnl['comment'],
2589 ''
2590 ))
2591 self._GRID_results_battery.panel_to_show = pnl
2592 #self._GRID_results_battery.Show()
2593 self._PNL_results_battery_grid.Show()
2594 self._GRID_results_battery.Fit()
2595 self._GRID_results_all.Fit()
2596 self.Layout()
2597 #--------------------------------------------------------
2600 #--------------------------------------------------------
2602 self._TCTRL_panel_comment.SetValue('')
2603 if self._PRW_panel.GetValue().strip() == '':
2604 self._GRID_results_battery.panel_to_show = None
2605 #self._GRID_results_battery.Hide()
2606 self._PNL_results_battery_grid.Hide()
2607 self.Layout()
2608 #--------------------------------------------------------
2609 # internal API
2610 #--------------------------------------------------------
2612 self.SetMinSize((10, 10))
2613
2614 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
2615
2616 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
2617 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
2618
2619 item = self.__action_button_popup.Append(-1, _('Plot'))
2620 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
2621
2622 item = self.__action_button_popup.Append(-1, _('Export to &file'))
2623 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item)
2624 self.__action_button_popup.Enable(id = menu_id, enable = False)
2625
2626 item = self.__action_button_popup.Append(-1, _('Export to &clipboard'))
2627 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item)
2628 self.__action_button_popup.Enable(id = menu_id, enable = False)
2629
2630 item = self.__action_button_popup.Append(-1, _('&Delete'))
2631 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
2632
2633 # FIXME: create inbox message to staff to phone patient to come in
2634 # FIXME: generate and let edit a SOAP narrative and include the values
2635
2636 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
2637 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
2638
2639 self._GRID_results_battery.show_by_panel = True
2640 self._GRID_results_battery.panel_to_show = None
2641 #self._GRID_results_battery.Hide()
2642 self._PNL_results_battery_grid.Hide()
2643 self._BTN_display_mode.SetLabel(_('All: by &Day'))
2644 #self._GRID_results_all.Show()
2645 self._PNL_results_all_grid.Show()
2646 self._PNL_results_all_listed.Hide()
2647 self.Layout()
2648
2649 self._PRW_panel.SetFocus()
2650 #--------------------------------------------------------
2651 # reget mixin API
2652 #--------------------------------------------------------
2654 pat = gmPerson.gmCurrentPatient()
2655 if pat.connected:
2656 self._GRID_results_battery.patient = pat
2657 if self.__display_mode == 'grid':
2658 self._GRID_results_all.patient = pat
2659 self._PNL_results_all_listed.patient = None
2660 else:
2661 self._GRID_results_all.patient = None
2662 self._PNL_results_all_listed.patient = pat
2663 else:
2664 self._GRID_results_battery.patient = None
2665 self._GRID_results_all.patient = None
2666 self._PNL_results_all_listed.patient = None
2667 return True
2668
2669 #================================================================
2670 # editing widgets
2671 #================================================================
2673
2674 if tests is None:
2675 return True
2676
2677 if len(tests) == 0:
2678 return True
2679
2680 if parent is None:
2681 parent = wx.GetApp().GetTopWindow()
2682
2683 if len(tests) > 10:
2684 test_count = len(tests)
2685 tests2show = None
2686 else:
2687 test_count = None
2688 tests2show = tests
2689 if len(tests) == 0:
2690 return True
2691
2692 dlg = cMeasurementsReviewDlg(parent, -1, tests = tests, test_count = test_count)
2693 decision = dlg.ShowModal()
2694 if decision != wx.ID_APPLY:
2695 return True
2696
2697 wx.BeginBusyCursor()
2698 if dlg._RBTN_confirm_abnormal.GetValue():
2699 abnormal = None
2700 elif dlg._RBTN_results_normal.GetValue():
2701 abnormal = False
2702 else:
2703 abnormal = True
2704
2705 if dlg._RBTN_confirm_relevance.GetValue():
2706 relevant = None
2707 elif dlg._RBTN_results_not_relevant.GetValue():
2708 relevant = False
2709 else:
2710 relevant = True
2711
2712 comment = None
2713 if len(tests) == 1:
2714 comment = dlg._TCTRL_comment.GetValue()
2715
2716 make_responsible = dlg._CHBOX_responsible.IsChecked()
2717 dlg.DestroyLater()
2718
2719 for test in tests:
2720 test.set_review (
2721 technically_abnormal = abnormal,
2722 clinically_relevant = relevant,
2723 comment = comment,
2724 make_me_responsible = make_responsible
2725 )
2726 wx.EndBusyCursor()
2727
2728 return True
2729
2730 #----------------------------------------------------------------
2731 from Gnumed.wxGladeWidgets import wxgMeasurementsReviewDlg
2732
2734
2736
2737 try:
2738 tests = kwargs['tests']
2739 del kwargs['tests']
2740 test_count = len(tests)
2741 try: del kwargs['test_count']
2742 except KeyError: pass
2743 except KeyError:
2744 tests = None
2745 test_count = kwargs['test_count']
2746 del kwargs['test_count']
2747
2748 wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs)
2749
2750 if tests is None:
2751 msg = _('%s results selected. Too many to list individually.') % test_count
2752 else:
2753 msg = '\n'.join (
2754 [ '%s: %s %s (%s)' % (
2755 t['unified_abbrev'],
2756 t['unified_val'],
2757 t['val_unit'],
2758 gmDateTime.pydt_strftime(t['clin_when'], '%Y %b %d')
2759 ) for t in tests
2760 ]
2761 )
2762
2763 self._LBL_tests.SetLabel(msg)
2764
2765 if test_count == 1:
2766 self._TCTRL_comment.Enable(True)
2767 self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], ''))
2768 if tests[0]['you_are_responsible']:
2769 self._CHBOX_responsible.Enable(False)
2770
2771 self.Fit()
2772 #--------------------------------------------------------
2773 # event handling
2774 #--------------------------------------------------------
2780
2781 #================================================================
2782 from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl
2783
2784 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
2785 """This edit area saves *new* measurements into the active patient only."""
2786
2788
2789 try:
2790 self.__default_date = kwargs['date']
2791 del kwargs['date']
2792 except KeyError:
2793 self.__default_date = None
2794
2795 wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl.__init__(self, *args, **kwargs)
2796 gmEditArea.cGenericEditAreaMixin.__init__(self)
2797
2798 self.__register_interests()
2799
2800 self.successful_save_msg = _('Successfully saved measurement.')
2801
2802 self._DPRW_evaluated.display_accuracy = gmDateTime.acc_minutes
2803
2804 #--------------------------------------------------------
2805 # generic edit area mixin API
2806 #----------------------------------------------------------------
2808 try:
2809 self._PRW_test.SetData(data = fields['pk_test_type']['data'])
2810 except KeyError:
2811 pass
2812 try:
2813 self._DPRW_evaluated.SetData(data = fields['clin_when']['data'])
2814 except KeyError:
2815 pass
2816 try:
2817 self._PRW_problem.SetData(data = fields['pk_episode']['data'])
2818 except KeyError:
2819 pass
2820 try:
2821 self._PRW_units.SetText(fields['val_unit']['data'], fields['val_unit']['data'], True)
2822 except KeyError:
2823 pass
2824 try:
2825 self._TCTRL_normal_min.SetValue(fields['val_normal_min']['data'])
2826 except KeyError:
2827 pass
2828 try:
2829 self._TCTRL_normal_max.SetValue(fields['val_normal_max']['data'])
2830 except KeyError:
2831 pass
2832 try:
2833 self._TCTRL_normal_range.SetValue(fields['val_normal_range']['data'])
2834 except KeyError:
2835 pass
2836 try:
2837 self._TCTRL_target_min.SetValue(fields['val_target_min']['data'])
2838 except KeyError:
2839 pass
2840 try:
2841 self._TCTRL_target_max.SetValue(fields['val_target_max']['data'])
2842 except KeyError:
2843 pass
2844 try:
2845 self._TCTRL_target_range.SetValue(fields['val_target_range']['data'])
2846 except KeyError:
2847 pass
2848
2849 self._TCTRL_result.SetFocus()
2850
2851 #--------------------------------------------------------
2853 self._PRW_test.SetText('', None, True)
2854 self.__refresh_loinc_info()
2855 self.__refresh_previous_value()
2856 self.__update_units_context()
2857 self._TCTRL_result.SetValue('')
2858 self._PRW_units.SetText('', None, True)
2859 self._PRW_abnormality_indicator.SetText('', None, True)
2860 if self.__default_date is None:
2861 self._DPRW_evaluated.SetData(data = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone))
2862 else:
2863 self._DPRW_evaluated.SetData(data = None)
2864 self._TCTRL_note_test_org.SetValue('')
2865 self._PRW_intended_reviewer.SetData(gmStaff.gmCurrentProvider()['pk_staff'])
2866 self._PRW_problem.SetData()
2867 self._TCTRL_narrative.SetValue('')
2868 self._CHBOX_review.SetValue(False)
2869 self._CHBOX_abnormal.SetValue(False)
2870 self._CHBOX_relevant.SetValue(False)
2871 self._CHBOX_abnormal.Enable(False)
2872 self._CHBOX_relevant.Enable(False)
2873 self._TCTRL_review_comment.SetValue('')
2874 self._TCTRL_normal_min.SetValue('')
2875 self._TCTRL_normal_max.SetValue('')
2876 self._TCTRL_normal_range.SetValue('')
2877 self._TCTRL_target_min.SetValue('')
2878 self._TCTRL_target_max.SetValue('')
2879 self._TCTRL_target_range.SetValue('')
2880 self._TCTRL_norm_ref_group.SetValue('')
2881
2882 self._PRW_test.SetFocus()
2883 #--------------------------------------------------------
2885 self._PRW_test.SetData(data = self.data['pk_test_type'])
2886 self.__refresh_loinc_info()
2887 self.__refresh_previous_value()
2888 self.__update_units_context()
2889 self._TCTRL_result.SetValue(self.data['unified_val'])
2890 self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True)
2891 self._PRW_abnormality_indicator.SetText (
2892 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2893 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2894 True
2895 )
2896 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2897 self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], ''))
2898 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2899 self._PRW_problem.SetData(self.data['pk_episode'])
2900 self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], ''))
2901 self._CHBOX_review.SetValue(False)
2902 self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False))
2903 self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False))
2904 self._CHBOX_abnormal.Enable(False)
2905 self._CHBOX_relevant.Enable(False)
2906 self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], ''))
2907 self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(self.data['val_normal_min'], '')))
2908 self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(self.data['val_normal_max'], '')))
2909 self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], ''))
2910 self._TCTRL_target_min.SetValue(str(gmTools.coalesce(self.data['val_target_min'], '')))
2911 self._TCTRL_target_max.SetValue(str(gmTools.coalesce(self.data['val_target_max'], '')))
2912 self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], ''))
2913 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], ''))
2914
2915 self._TCTRL_result.SetFocus()
2916 #--------------------------------------------------------
2918 self._PRW_test.SetText('', None, True)
2919 self.__refresh_loinc_info()
2920 self.__refresh_previous_value()
2921 self.__update_units_context()
2922 self._TCTRL_result.SetValue('')
2923 self._PRW_units.SetText('', None, True)
2924 self._PRW_abnormality_indicator.SetText('', None, True)
2925 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2926 self._TCTRL_note_test_org.SetValue('')
2927 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2928 self._PRW_problem.SetData(self.data['pk_episode'])
2929 self._TCTRL_narrative.SetValue('')
2930 self._CHBOX_review.SetValue(False)
2931 self._CHBOX_abnormal.SetValue(False)
2932 self._CHBOX_relevant.SetValue(False)
2933 self._CHBOX_abnormal.Enable(False)
2934 self._CHBOX_relevant.Enable(False)
2935 self._TCTRL_review_comment.SetValue('')
2936 self._TCTRL_normal_min.SetValue('')
2937 self._TCTRL_normal_max.SetValue('')
2938 self._TCTRL_normal_range.SetValue('')
2939 self._TCTRL_target_min.SetValue('')
2940 self._TCTRL_target_max.SetValue('')
2941 self._TCTRL_target_range.SetValue('')
2942 self._TCTRL_norm_ref_group.SetValue('')
2943
2944 self._PRW_test.SetFocus()
2945 #--------------------------------------------------------
2947
2948 validity = True
2949
2950 if not self._DPRW_evaluated.is_valid_timestamp():
2951 self._DPRW_evaluated.display_as_valid(False)
2952 validity = False
2953 else:
2954 self._DPRW_evaluated.display_as_valid(True)
2955
2956 val = self._TCTRL_result.GetValue().strip()
2957 if val == '':
2958 validity = False
2959 self.display_ctrl_as_valid(self._TCTRL_result, False)
2960 else:
2961 self.display_ctrl_as_valid(self._TCTRL_result, True)
2962 numeric, val = gmTools.input2decimal(val)
2963 if numeric:
2964 if self._PRW_units.GetValue().strip() == '':
2965 self._PRW_units.display_as_valid(False)
2966 validity = False
2967 else:
2968 self._PRW_units.display_as_valid(True)
2969 else:
2970 self._PRW_units.display_as_valid(True)
2971
2972 if self._PRW_problem.GetValue().strip() == '':
2973 self._PRW_problem.display_as_valid(False)
2974 validity = False
2975 else:
2976 self._PRW_problem.display_as_valid(True)
2977
2978 if self._PRW_test.GetValue().strip() == '':
2979 self._PRW_test.display_as_valid(False)
2980 validity = False
2981 else:
2982 self._PRW_test.display_as_valid(True)
2983
2984 if self._PRW_intended_reviewer.GetData() is None:
2985 self._PRW_intended_reviewer.display_as_valid(False)
2986 validity = False
2987 else:
2988 self._PRW_intended_reviewer.display_as_valid(True)
2989
2990 ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max]
2991 for widget in ctrls:
2992 val = widget.GetValue().strip()
2993 if val == '':
2994 continue
2995 try:
2996 decimal.Decimal(val.replace(',', '.', 1))
2997 self.display_ctrl_as_valid(widget, True)
2998 except:
2999 validity = False
3000 self.display_ctrl_as_valid(widget, False)
3001
3002 if validity is False:
3003 self.StatusText = _('Cannot save result. Invalid or missing essential input.')
3004
3005 return validity
3006 #--------------------------------------------------------
3008
3009 emr = gmPerson.gmCurrentPatient().emr
3010
3011 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
3012 if success:
3013 v_num = result
3014 v_al = None
3015 else:
3016 v_al = self._TCTRL_result.GetValue().strip()
3017 v_num = None
3018
3019 pk_type = self._PRW_test.GetData()
3020 if pk_type is None:
3021 abbrev = self._PRW_test.GetValue().strip()
3022 name = self._PRW_test.GetValue().strip()
3023 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3024 lab = manage_measurement_orgs (
3025 parent = self,
3026 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
3027 )
3028 if lab is not None:
3029 lab = lab['pk_test_org']
3030 tt = gmPathLab.create_measurement_type (
3031 lab = lab,
3032 abbrev = abbrev,
3033 name = name,
3034 unit = unit
3035 )
3036 pk_type = tt['pk_test_type']
3037
3038 tr = emr.add_test_result (
3039 episode = self._PRW_problem.GetData(can_create=True, is_open=False),
3040 type = pk_type,
3041 intended_reviewer = self._PRW_intended_reviewer.GetData(),
3042 val_num = v_num,
3043 val_alpha = v_al,
3044 unit = self._PRW_units.GetValue()
3045 )
3046
3047 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
3048
3049 ctrls = [
3050 ('abnormality_indicator', self._PRW_abnormality_indicator),
3051 ('note_test_org', self._TCTRL_note_test_org),
3052 ('comment', self._TCTRL_narrative),
3053 ('val_normal_range', self._TCTRL_normal_range),
3054 ('val_target_range', self._TCTRL_target_range),
3055 ('norm_ref_group', self._TCTRL_norm_ref_group)
3056 ]
3057 for field, widget in ctrls:
3058 tr[field] = widget.GetValue().strip()
3059
3060 ctrls = [
3061 ('val_normal_min', self._TCTRL_normal_min),
3062 ('val_normal_max', self._TCTRL_normal_max),
3063 ('val_target_min', self._TCTRL_target_min),
3064 ('val_target_max', self._TCTRL_target_max)
3065 ]
3066 for field, widget in ctrls:
3067 val = widget.GetValue().strip()
3068 if val == '':
3069 tr[field] = None
3070 else:
3071 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
3072
3073 tr.save_payload()
3074
3075 if self._CHBOX_review.GetValue() is True:
3076 tr.set_review (
3077 technically_abnormal = self._CHBOX_abnormal.GetValue(),
3078 clinically_relevant = self._CHBOX_relevant.GetValue(),
3079 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
3080 make_me_responsible = False
3081 )
3082
3083 self.data = tr
3084
3085 # wx.CallAfter (
3086 # plot_adjacent_measurements,
3087 # test = self.data,
3088 # plot_singular_result = False,
3089 # use_default_template = True
3090 # )
3091
3092 return True
3093 #--------------------------------------------------------
3095
3096 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
3097 if success:
3098 v_num = result
3099 v_al = None
3100 else:
3101 v_num = None
3102 v_al = self._TCTRL_result.GetValue().strip()
3103
3104 pk_type = self._PRW_test.GetData()
3105 if pk_type is None:
3106 abbrev = self._PRW_test.GetValue().strip()
3107 name = self._PRW_test.GetValue().strip()
3108 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3109 lab = manage_measurement_orgs (
3110 parent = self,
3111 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
3112 )
3113 if lab is not None:
3114 lab = lab['pk_test_org']
3115 tt = gmPathLab.create_measurement_type (
3116 lab = None,
3117 abbrev = abbrev,
3118 name = name,
3119 unit = unit
3120 )
3121 pk_type = tt['pk_test_type']
3122
3123 tr = self.data
3124
3125 tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False)
3126 tr['pk_test_type'] = pk_type
3127 tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData()
3128 tr['val_num'] = v_num
3129 tr['val_alpha'] = v_al
3130 tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3131 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
3132
3133 ctrls = [
3134 ('abnormality_indicator', self._PRW_abnormality_indicator),
3135 ('note_test_org', self._TCTRL_note_test_org),
3136 ('comment', self._TCTRL_narrative),
3137 ('val_normal_range', self._TCTRL_normal_range),
3138 ('val_target_range', self._TCTRL_target_range),
3139 ('norm_ref_group', self._TCTRL_norm_ref_group)
3140 ]
3141 for field, widget in ctrls:
3142 tr[field] = widget.GetValue().strip()
3143
3144 ctrls = [
3145 ('val_normal_min', self._TCTRL_normal_min),
3146 ('val_normal_max', self._TCTRL_normal_max),
3147 ('val_target_min', self._TCTRL_target_min),
3148 ('val_target_max', self._TCTRL_target_max)
3149 ]
3150 for field, widget in ctrls:
3151 val = widget.GetValue().strip()
3152 if val == '':
3153 tr[field] = None
3154 else:
3155 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
3156
3157 tr.save_payload()
3158
3159 if self._CHBOX_review.GetValue() is True:
3160 tr.set_review (
3161 technically_abnormal = self._CHBOX_abnormal.GetValue(),
3162 clinically_relevant = self._CHBOX_relevant.GetValue(),
3163 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
3164 make_me_responsible = False
3165 )
3166
3167 # wx.CallAfter (
3168 # plot_adjacent_measurements,
3169 # test = self.data,
3170 # plot_singular_result = False,
3171 # use_default_template = True
3172 # )
3173
3174 return True
3175 #--------------------------------------------------------
3176 # event handling
3177 #--------------------------------------------------------
3179 self._PRW_test.add_callback_on_lose_focus(self._on_leave_test_prw)
3180 self._PRW_abnormality_indicator.add_callback_on_lose_focus(self._on_leave_indicator_prw)
3181 self._PRW_units.add_callback_on_lose_focus(self._on_leave_unit_prw)
3182 #--------------------------------------------------------
3184 self.__refresh_loinc_info()
3185 self.__refresh_previous_value()
3186 self.__update_units_context()
3187 # only works if we've got a unit set
3188 self.__update_normal_range()
3189 self.__update_clinical_range()
3190 #--------------------------------------------------------
3192 # maybe we've got a unit now ?
3193 self.__update_normal_range()
3194 self.__update_clinical_range()
3195 #--------------------------------------------------------
3197 # if the user hasn't explicitly enabled reviewing
3198 if not self._CHBOX_review.GetValue():
3199 self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != '')
3200 #--------------------------------------------------------
3202 self._CHBOX_abnormal.Enable(self._CHBOX_review.GetValue())
3203 self._CHBOX_relevant.Enable(self._CHBOX_review.GetValue())
3204 self._TCTRL_review_comment.Enable(self._CHBOX_review.GetValue())
3205 #--------------------------------------------------------
3221 #--------------------------------------------------------
3225 #--------------------------------------------------------
3226 # internal helpers
3227 #--------------------------------------------------------
3229
3230 if self._PRW_test.GetData() is None:
3231 self._PRW_units.unset_context(context = 'pk_type')
3232 self._PRW_units.unset_context(context = 'loinc')
3233 if self._PRW_test.GetValue().strip() == '':
3234 self._PRW_units.unset_context(context = 'test_name')
3235 else:
3236 self._PRW_units.set_context(context = 'test_name', val = self._PRW_test.GetValue().strip())
3237 return
3238
3239 tt = self._PRW_test.GetData(as_instance = True)
3240
3241 self._PRW_units.set_context(context = 'pk_type', val = tt['pk_test_type'])
3242 self._PRW_units.set_context(context = 'test_name', val = tt['name'])
3243
3244 if tt['loinc'] is not None:
3245 self._PRW_units.set_context(context = 'loinc', val = tt['loinc'])
3246
3247 # closest unit
3248 if self._PRW_units.GetValue().strip() == '':
3249 clin_when = self._DPRW_evaluated.GetData()
3250 if clin_when is None:
3251 unit = tt.temporally_closest_unit
3252 else:
3253 clin_when = clin_when.get_pydt()
3254 unit = tt.get_temporally_closest_unit(timestamp = clin_when)
3255 if unit is None:
3256 self._PRW_units.SetText('', unit, True)
3257 else:
3258 self._PRW_units.SetText(unit, unit, True)
3259
3260 #--------------------------------------------------------
3262 unit = self._PRW_units.GetValue().strip()
3263 if unit == '':
3264 return
3265 if self._PRW_test.GetData() is None:
3266 return
3267 for ctrl in [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_normal_range, self._TCTRL_norm_ref_group]:
3268 if ctrl.GetValue().strip() != '':
3269 return
3270 tt = self._PRW_test.GetData(as_instance = True)
3271 test_w_range = tt.get_temporally_closest_normal_range (
3272 unit,
3273 timestamp = self._DPRW_evaluated.GetData().get_pydt()
3274 )
3275 if test_w_range is None:
3276 return
3277 self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(test_w_range['val_normal_min'], '')))
3278 self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(test_w_range['val_normal_max'], '')))
3279 self._TCTRL_normal_range.SetValue(gmTools.coalesce(test_w_range['val_normal_range'], ''))
3280 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(test_w_range['norm_ref_group'], ''))
3281
3282 #--------------------------------------------------------
3284 unit = self._PRW_units.GetValue().strip()
3285 if unit == '':
3286 return
3287 if self._PRW_test.GetData() is None:
3288 return
3289 for ctrl in [self._TCTRL_target_min, self._TCTRL_target_max, self._TCTRL_target_range]:
3290 if ctrl.GetValue().strip() != '':
3291 return
3292 tt = self._PRW_test.GetData(as_instance = True)
3293 test_w_range = tt.get_temporally_closest_target_range (
3294 unit,
3295 gmPerson.gmCurrentPatient().ID,
3296 timestamp = self._DPRW_evaluated.GetData().get_pydt()
3297 )
3298 if test_w_range is None:
3299 return
3300 self._TCTRL_target_min.SetValue(str(gmTools.coalesce(test_w_range['val_target_min'], '')))
3301 self._TCTRL_target_max.SetValue(str(gmTools.coalesce(test_w_range['val_target_max'], '')))
3302 self._TCTRL_target_range.SetValue(gmTools.coalesce(test_w_range['val_target_range'], ''))
3303
3304 #--------------------------------------------------------
3306
3307 self._TCTRL_loinc.SetValue('')
3308
3309 if self._PRW_test.GetData() is None:
3310 return
3311
3312 tt = self._PRW_test.GetData(as_instance = True)
3313
3314 if tt['loinc'] is None:
3315 return
3316
3317 info = gmLOINC.loinc2term(loinc = tt['loinc'])
3318 if len(info) == 0:
3319 self._TCTRL_loinc.SetValue('')
3320 return
3321
3322 self._TCTRL_loinc.SetValue('%s: %s' % (tt['loinc'], info[0]))
3323 #--------------------------------------------------------
3325 self._TCTRL_previous_value.SetValue('')
3326 # it doesn't make much sense to show the most
3327 # recent value when editing an existing one
3328 if self.data is not None:
3329 return
3330 if self._PRW_test.GetData() is None:
3331 return
3332 tt = self._PRW_test.GetData(as_instance = True)
3333 most_recent = tt.get_most_recent_results (
3334 no_of_results = 1,
3335 patient = gmPerson.gmCurrentPatient().ID
3336 )
3337 if most_recent is None:
3338 return
3339 self._TCTRL_previous_value.SetValue(_('%s ago: %s%s%s - %s%s') % (
3340 gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - most_recent['clin_when']),
3341 most_recent['unified_val'],
3342 most_recent['val_unit'],
3343 gmTools.coalesce(most_recent['abnormality_indicator'], '', ' (%s)'),
3344 most_recent['abbrev_tt'],
3345 gmTools.coalesce(most_recent.formatted_range, '', ' [%s]')
3346 ))
3347 self._TCTRL_previous_value.SetToolTip(most_recent.format (
3348 with_review = True,
3349 with_evaluation = False,
3350 with_ranges = True,
3351 with_episode = True,
3352 with_type_details=True
3353 ))
3354
3355 #================================================================
3356 # measurement type handling
3357 #================================================================
3359
3360 if parent is None:
3361 parent = wx.GetApp().GetTopWindow()
3362
3363 if msg is None:
3364 msg = _('Pick the relevant measurement types.')
3365
3366 if right_column is None:
3367 right_columns = [_('Picked')]
3368 else:
3369 right_columns = [right_column]
3370
3371 picker = gmListWidgets.cItemPickerDlg(parent, -1, msg = msg)
3372 picker.set_columns(columns = [_('Known measurement types')], columns_right = right_columns)
3373 types = gmPathLab.get_measurement_types(order_by = 'unified_abbrev')
3374 picker.set_choices (
3375 choices = [
3376 '%s: %s%s' % (
3377 t['unified_abbrev'],
3378 t['unified_name'],
3379 gmTools.coalesce(t['name_org'], '', ' (%s)')
3380 )
3381 for t in types
3382 ],
3383 data = types
3384 )
3385 if picks is not None:
3386 picker.set_picks (
3387 picks = [
3388 '%s: %s%s' % (
3389 p['unified_abbrev'],
3390 p['unified_name'],
3391 gmTools.coalesce(p['name_org'], '', ' (%s)')
3392 )
3393 for p in picks
3394 ],
3395 data = picks
3396 )
3397 result = picker.ShowModal()
3398
3399 if result == wx.ID_CANCEL:
3400 picker.DestroyLater()
3401 return None
3402
3403 picks = picker.picks
3404 picker.DestroyLater()
3405 return picks
3406
3407 #----------------------------------------------------------------
3409
3410 if parent is None:
3411 parent = wx.GetApp().GetTopWindow()
3412
3413 #------------------------------------------------------------
3414 def edit(test_type=None):
3415 ea = cMeasurementTypeEAPnl(parent, -1, type = test_type)
3416 dlg = gmEditArea.cGenericEditAreaDlg2 (
3417 parent = parent,
3418 id = -1,
3419 edit_area = ea,
3420 single_entry = gmTools.bool2subst((test_type is None), False, True)
3421 )
3422 dlg.SetTitle(gmTools.coalesce(test_type, _('Adding measurement type'), _('Editing measurement type')))
3423
3424 if dlg.ShowModal() == wx.ID_OK:
3425 dlg.DestroyLater()
3426 return True
3427
3428 dlg.DestroyLater()
3429 return False
3430
3431 #------------------------------------------------------------
3432 def delete(measurement_type):
3433 if measurement_type.in_use:
3434 gmDispatcher.send (
3435 signal = 'statustext',
3436 beep = True,
3437 msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev'])
3438 )
3439 return False
3440 gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type'])
3441 return True
3442
3443 #------------------------------------------------------------
3444 def get_tooltip(test_type):
3445 return test_type.format()
3446
3447 #------------------------------------------------------------
3448 def manage_aggregates(test_type):
3449 manage_meta_test_types(parent = parent)
3450 return False
3451
3452 #------------------------------------------------------------
3453 def manage_panels_of_type(test_type):
3454 if test_type['loinc'] is None:
3455 return False
3456 all_panels = gmPathLab.get_test_panels(order_by = 'description')
3457 curr_panels = test_type.test_panels
3458 if curr_panels is None:
3459 curr_panels = []
3460 panel_candidates = [ p for p in all_panels if p['pk_test_panel'] not in [
3461 c_pnl['pk_test_panel'] for c_pnl in curr_panels
3462 ] ]
3463 picker = gmListWidgets.cItemPickerDlg(parent, -1, title = 'Panels with [%s]' % test_type['abbrev'])
3464 picker.set_columns(['Panels available'], ['Panels [%s] is to be on' % test_type['abbrev']])
3465 picker.set_choices (
3466 choices = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in panel_candidates ],
3467 data = panel_candidates
3468 )
3469 picker.set_picks (
3470 picks = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in curr_panels ],
3471 data = curr_panels
3472 )
3473 exit_type = picker.ShowModal()
3474 if exit_type == wx.ID_CANCEL:
3475 return False
3476
3477 # add picked panels which aren't currently in the panel list
3478 panels2add = [ p for p in picker.picks if p['pk_test_panel'] not in [
3479 c_pnl['pk_test_panel'] for c_pnl in curr_panels
3480 ] ]
3481 # remove unpicked panels off the current panel list
3482 panels2remove = [ p for p in curr_panels if p['pk_test_panel'] not in [
3483 picked_pnl['pk_test_panel'] for picked_pnl in picker.picks
3484 ] ]
3485 for new_panel in panels2add:
3486 new_panel.add_loinc(test_type['loinc'])
3487 for stale_panel in panels2remove:
3488 stale_panel.remove_loinc(test_type['loinc'])
3489
3490 return True
3491
3492 #------------------------------------------------------------
3493 def refresh(lctrl):
3494 mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev')
3495 items = [ [
3496 m['abbrev'],
3497 m['name'],
3498 gmTools.coalesce(m['reference_unit'], ''),
3499 gmTools.coalesce(m['loinc'], ''),
3500 gmTools.coalesce(m['comment_type'], ''),
3501 gmTools.coalesce(m['name_org'], '?'),
3502 gmTools.coalesce(m['comment_org'], ''),
3503 m['pk_test_type']
3504 ] for m in mtypes ]
3505 lctrl.set_string_items(items)
3506 lctrl.set_data(mtypes)
3507
3508 #------------------------------------------------------------
3509 gmListWidgets.get_choices_from_list (
3510 parent = parent,
3511 caption = _('Measurement types.'),
3512 columns = [ _('Abbrev'), _('Name'), _('Unit'), _('LOINC'), _('Comment'), _('Org'), _('Comment'), '#' ],
3513 single_selection = True,
3514 refresh_callback = refresh,
3515 edit_callback = edit,
3516 new_callback = edit,
3517 delete_callback = delete,
3518 list_tooltip_callback = get_tooltip,
3519 left_extra_button = (_('%s &Aggregate') % gmTools.u_sum, _('Manage aggregations (%s) of tests into groups.') % gmTools.u_sum, manage_aggregates),
3520 middle_extra_button = (_('Select panels'), _('Select panels the focussed test type is to belong to.'), manage_panels_of_type)
3521 )
3522
3523 #----------------------------------------------------------------
3525
3527
3528 query = """
3529 SELECT DISTINCT ON (field_label)
3530 pk_test_type AS data,
3531 name
3532 || ' ('
3533 || coalesce (
3534 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
3535 '%(in_house)s'
3536 )
3537 || ')'
3538 AS field_label,
3539 name
3540 || ' ('
3541 || abbrev || ', '
3542 || coalesce(abbrev_meta || ': ' || name_meta || ', ', '')
3543 || coalesce (
3544 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
3545 '%(in_house)s'
3546 )
3547 || ')'
3548 AS list_label
3549 FROM
3550 clin.v_test_types c_vtt
3551 WHERE
3552 abbrev_meta %%(fragment_condition)s
3553 OR
3554 name_meta %%(fragment_condition)s
3555 OR
3556 abbrev %%(fragment_condition)s
3557 OR
3558 name %%(fragment_condition)s
3559 ORDER BY field_label
3560 LIMIT 50""" % {'in_house': _('generic / in house lab')}
3561
3562 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3563 mp.setThresholds(1, 2, 4)
3564 mp.word_separators = '[ \t:@]+'
3565 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
3566 self.matcher = mp
3567 self.SetToolTip(_('Select the type of measurement.'))
3568 self.selection_only = False
3569
3570 #------------------------------------------------------------
3572 if self.GetData() is None:
3573 return None
3574
3575 return gmPathLab.cMeasurementType(aPK_obj = self.GetData())
3576
3577 #----------------------------------------------------------------
3578 from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl
3579
3580 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
3581
3583
3584 try:
3585 data = kwargs['type']
3586 del kwargs['type']
3587 except KeyError:
3588 data = None
3589
3590 wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl.__init__(self, *args, **kwargs)
3591 gmEditArea.cGenericEditAreaMixin.__init__(self)
3592 self.mode = 'new'
3593 self.data = data
3594 if data is not None:
3595 self.mode = 'edit'
3596
3597 self.__init_ui()
3598
3599 #----------------------------------------------------------------
3601
3602 # name phraseweel
3603 query = """
3604 select distinct on (name)
3605 pk,
3606 name
3607 from clin.test_type
3608 where
3609 name %(fragment_condition)s
3610 order by name
3611 limit 50"""
3612 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3613 mp.setThresholds(1, 2, 4)
3614 self._PRW_name.matcher = mp
3615 self._PRW_name.selection_only = False
3616 self._PRW_name.add_callback_on_lose_focus(callback = self._on_name_lost_focus)
3617
3618 # abbreviation
3619 query = """
3620 select distinct on (abbrev)
3621 pk,
3622 abbrev
3623 from clin.test_type
3624 where
3625 abbrev %(fragment_condition)s
3626 order by abbrev
3627 limit 50"""
3628 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3629 mp.setThresholds(1, 2, 3)
3630 self._PRW_abbrev.matcher = mp
3631 self._PRW_abbrev.selection_only = False
3632
3633 # unit
3634 self._PRW_reference_unit.selection_only = False
3635
3636 # loinc
3637 mp = gmLOINC.cLOINCMatchProvider()
3638 mp.setThresholds(1, 2, 4)
3639 #mp.print_queries = True
3640 #mp.word_separators = '[ \t:@]+'
3641 self._PRW_loinc.matcher = mp
3642 self._PRW_loinc.selection_only = False
3643 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
3644
3645 #----------------------------------------------------------------
3647
3648 test = self._PRW_name.GetValue().strip()
3649
3650 if test == '':
3651 self._PRW_reference_unit.unset_context(context = 'test_name')
3652 return
3653
3654 self._PRW_reference_unit.set_context(context = 'test_name', val = test)
3655
3656 #----------------------------------------------------------------
3658 loinc = self._PRW_loinc.GetData()
3659
3660 if loinc is None:
3661 self._TCTRL_loinc_info.SetValue('')
3662 self._PRW_reference_unit.unset_context(context = 'loinc')
3663 return
3664
3665 self._PRW_reference_unit.set_context(context = 'loinc', val = loinc)
3666
3667 info = gmLOINC.loinc2term(loinc = loinc)
3668 if len(info) == 0:
3669 self._TCTRL_loinc_info.SetValue('')
3670 return
3671
3672 self._TCTRL_loinc_info.SetValue(info[0])
3673
3674 #----------------------------------------------------------------
3675 # generic Edit Area mixin API
3676 #----------------------------------------------------------------
3678
3679 has_errors = False
3680 for field in [self._PRW_name, self._PRW_abbrev, self._PRW_reference_unit]:
3681 if field.GetValue().strip() in ['', None]:
3682 has_errors = True
3683 field.display_as_valid(valid = False)
3684 else:
3685 field.display_as_valid(valid = True)
3686 field.Refresh()
3687
3688 return (not has_errors)
3689
3690 #----------------------------------------------------------------
3692
3693 pk_org = self._PRW_test_org.GetData()
3694 if pk_org is None:
3695 pk_org = gmPathLab.create_test_org (
3696 name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), '')
3697 )['pk_test_org']
3698
3699 tt = gmPathLab.create_measurement_type (
3700 lab = pk_org,
3701 abbrev = self._PRW_abbrev.GetValue().strip(),
3702 name = self._PRW_name.GetValue().strip(),
3703 unit = gmTools.coalesce (
3704 self._PRW_reference_unit.GetData(),
3705 self._PRW_reference_unit.GetValue()
3706 ).strip()
3707 )
3708 if self._PRW_loinc.GetData() is not None:
3709 tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), '')
3710 else:
3711 tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), '')
3712 tt['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), '')
3713 tt['pk_meta_test_type'] = self._PRW_meta_type.GetData()
3714
3715 tt.save()
3716
3717 self.data = tt
3718
3719 return True
3720 #----------------------------------------------------------------
3722
3723 pk_org = self._PRW_test_org.GetData()
3724 if pk_org is None:
3725 pk_org = gmPathLab.create_test_org (
3726 name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), '')
3727 )['pk_test_org']
3728
3729 self.data['pk_test_org'] = pk_org
3730 self.data['abbrev'] = self._PRW_abbrev.GetValue().strip()
3731 self.data['name'] = self._PRW_name.GetValue().strip()
3732 self.data['reference_unit'] = gmTools.coalesce (
3733 self._PRW_reference_unit.GetData(),
3734 self._PRW_reference_unit.GetValue()
3735 ).strip()
3736 old_loinc = self.data['loinc']
3737 if self._PRW_loinc.GetData() is not None:
3738 self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), '')
3739 else:
3740 self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), '')
3741 new_loinc = self.data['loinc']
3742 self.data['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), '')
3743 self.data['pk_meta_test_type'] = self._PRW_meta_type.GetData()
3744 self.data.save()
3745
3746 # was it, AND can it be, on any panel ?
3747 if None not in [old_loinc, new_loinc]:
3748 # would it risk being dropped from any panel ?
3749 if new_loinc != old_loinc:
3750 for panel in gmPathLab.get_test_panels(loincs = [old_loinc]):
3751 pnl_loincs = panel.included_loincs
3752 if new_loinc not in pnl_loincs:
3753 pnl_loincs.append(new_loinc)
3754 panel.included_loincs = pnl_loincs
3755 # do not remove old_loinc as it may sit on another
3756 # test type which we haven't removed it from yet
3757
3758 return True
3759
3760 #----------------------------------------------------------------
3762 self._PRW_name.SetText('', None, True)
3763 self._on_name_lost_focus()
3764 self._PRW_abbrev.SetText('', None, True)
3765 self._PRW_reference_unit.SetText('', None, True)
3766 self._PRW_loinc.SetText('', None, True)
3767 self._on_loinc_lost_focus()
3768 self._TCTRL_comment_type.SetValue('')
3769 self._PRW_test_org.SetText('', None, True)
3770 self._PRW_meta_type.SetText('', None, True)
3771
3772 self._PRW_name.SetFocus()
3773 #----------------------------------------------------------------
3775 self._PRW_name.SetText(self.data['name'], self.data['name'], True)
3776 self._on_name_lost_focus()
3777 self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True)
3778 self._PRW_reference_unit.SetText (
3779 gmTools.coalesce(self.data['reference_unit'], ''),
3780 self.data['reference_unit'],
3781 True
3782 )
3783 self._PRW_loinc.SetText (
3784 gmTools.coalesce(self.data['loinc'], ''),
3785 self.data['loinc'],
3786 True
3787 )
3788 self._on_loinc_lost_focus()
3789 self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], ''))
3790 self._PRW_test_org.SetText (
3791 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
3792 self.data['pk_test_org'],
3793 True
3794 )
3795 if self.data['pk_meta_test_type'] is None:
3796 self._PRW_meta_type.SetText('', None, True)
3797 else:
3798 self._PRW_meta_type.SetText('%s: %s' % (self.data['abbrev_meta'], self.data['name_meta']), self.data['pk_meta_test_type'], True)
3799
3800 self._PRW_name.SetFocus()
3801 #----------------------------------------------------------------
3810
3811 #================================================================
3812 _SQL_units_from_test_results = """
3813 -- via clin.v_test_results.pk_type (for types already used in results)
3814 SELECT
3815 val_unit AS data,
3816 val_unit AS field_label,
3817 val_unit || ' (' || name_tt || ')' AS list_label,
3818 1 AS rank
3819 FROM
3820 clin.v_test_results
3821 WHERE
3822 (
3823 val_unit %(fragment_condition)s
3824 OR
3825 reference_unit %(fragment_condition)s
3826 )
3827 %(ctxt_type_pk)s
3828 %(ctxt_test_name)s
3829 """
3830
3831 _SQL_units_from_test_types = """
3832 -- via clin.test_type (for types not yet used in results)
3833 SELECT
3834 reference_unit AS data,
3835 reference_unit AS field_label,
3836 reference_unit || ' (' || name || ')' AS list_label,
3837 2 AS rank
3838 FROM
3839 clin.test_type
3840 WHERE
3841 reference_unit %(fragment_condition)s
3842 %(ctxt_ctt)s
3843 """
3844
3845 _SQL_units_from_loinc_ipcc = """
3846 -- via ref.loinc.ipcc_units
3847 SELECT
3848 ipcc_units AS data,
3849 ipcc_units AS field_label,
3850 ipcc_units || ' (LOINC.ipcc: ' || term || ')' AS list_label,
3851 3 AS rank
3852 FROM
3853 ref.loinc
3854 WHERE
3855 ipcc_units %(fragment_condition)s
3856 %(ctxt_loinc)s
3857 %(ctxt_loinc_term)s
3858 """
3859
3860 _SQL_units_from_loinc_submitted = """
3861 -- via ref.loinc.submitted_units
3862 SELECT
3863 submitted_units AS data,
3864 submitted_units AS field_label,
3865 submitted_units || ' (LOINC.submitted:' || term || ')' AS list_label,
3866 3 AS rank
3867 FROM
3868 ref.loinc
3869 WHERE
3870 submitted_units %(fragment_condition)s
3871 %(ctxt_loinc)s
3872 %(ctxt_loinc_term)s
3873 """
3874
3875 _SQL_units_from_loinc_example = """
3876 -- via ref.loinc.example_units
3877 SELECT
3878 example_units AS data,
3879 example_units AS field_label,
3880 example_units || ' (LOINC.example: ' || term || ')' AS list_label,
3881 3 AS rank
3882 FROM
3883 ref.loinc
3884 WHERE
3885 example_units %(fragment_condition)s
3886 %(ctxt_loinc)s
3887 %(ctxt_loinc_term)s
3888 """
3889
3890 _SQL_units_from_substance_doses = """
3891 -- via ref.v_substance_doses.unit
3892 SELECT
3893 unit AS data,
3894 unit AS field_label,
3895 unit || ' (' || substance || ')' AS list_label,
3896 2 AS rank
3897 FROM
3898 ref.v_substance_doses
3899 WHERE
3900 unit %(fragment_condition)s
3901 %(ctxt_substance)s
3902 """
3903
3904 _SQL_units_from_substance_doses2 = """
3905 -- via ref.v_substance_doses.dose_unit
3906 SELECT
3907 dose_unit AS data,
3908 dose_unit AS field_label,
3909 dose_unit || ' (' || substance || ')' AS list_label,
3910 2 AS rank
3911 FROM
3912 ref.v_substance_doses
3913 WHERE
3914 dose_unit %(fragment_condition)s
3915 %(ctxt_substance)s
3916 """
3917
3918 #----------------------------------------------------------------
3920
3922
3923 query = """
3924 SELECT DISTINCT ON (data)
3925 data,
3926 field_label,
3927 list_label
3928 FROM (
3929
3930 SELECT
3931 data,
3932 field_label,
3933 list_label,
3934 rank
3935 FROM (
3936 (%s) UNION ALL
3937 (%s) UNION ALL
3938 (%s) UNION ALL
3939 (%s) UNION ALL
3940 (%s) UNION ALL
3941 (%s) UNION ALL
3942 (%s)
3943 ) AS all_matching_units
3944 WHERE data IS NOT NULL
3945 ORDER BY rank, list_label
3946
3947 ) AS ranked_matching_units
3948 LIMIT 50""" % (
3949 _SQL_units_from_test_results,
3950 _SQL_units_from_test_types,
3951 _SQL_units_from_loinc_ipcc,
3952 _SQL_units_from_loinc_submitted,
3953 _SQL_units_from_loinc_example,
3954 _SQL_units_from_substance_doses,
3955 _SQL_units_from_substance_doses2
3956 )
3957
3958 ctxt = {
3959 'ctxt_type_pk': {
3960 'where_part': 'AND pk_test_type = %(pk_type)s',
3961 'placeholder': 'pk_type'
3962 },
3963 'ctxt_test_name': {
3964 'where_part': 'AND %(test_name)s IN (name_tt, name_meta, abbrev_meta)',
3965 'placeholder': 'test_name'
3966 },
3967 'ctxt_ctt': {
3968 'where_part': 'AND %(test_name)s IN (name, abbrev)',
3969 'placeholder': 'test_name'
3970 },
3971 'ctxt_loinc': {
3972 'where_part': 'AND code = %(loinc)s',
3973 'placeholder': 'loinc'
3974 },
3975 'ctxt_loinc_term': {
3976 'where_part': 'AND term ~* %(test_name)s',
3977 'placeholder': 'test_name'
3978 },
3979 'ctxt_substance': {
3980 'where_part': 'AND description ~* %(substance)s',
3981 'placeholder': 'substance'
3982 }
3983 }
3984
3985 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = ctxt)
3986 mp.setThresholds(1, 2, 4)
3987 #mp.print_queries = True
3988 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
3989 self.matcher = mp
3990 self.SetToolTip(_('Select the desired unit for the amount or measurement.'))
3991 self.selection_only = False
3992 self.phrase_separators = '[;|]+'
3993
3994 #================================================================
3995
3996 #================================================================
3998
4000
4001 query = """
4002 select distinct abnormality_indicator,
4003 abnormality_indicator, abnormality_indicator
4004 from clin.v_test_results
4005 where
4006 abnormality_indicator %(fragment_condition)s
4007 order by abnormality_indicator
4008 limit 25"""
4009
4010 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4011 mp.setThresholds(1, 1, 2)
4012 mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"'
4013 mp.word_separators = '[ \t&:]+'
4014 gmPhraseWheel.cPhraseWheel.__init__ (
4015 self,
4016 *args,
4017 **kwargs
4018 )
4019 self.matcher = mp
4020 self.SetToolTip(_('Select an indicator for the level of abnormality.'))
4021 self.selection_only = False
4022
4023 #================================================================
4024 # measurement org widgets / functions
4025 #----------------------------------------------------------------
4027 ea = cMeasurementOrgEAPnl(parent, -1)
4028 ea.data = org
4029 ea.mode = gmTools.coalesce(org, 'new', 'edit')
4030 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea)
4031 dlg.SetTitle(gmTools.coalesce(org, _('Adding new diagnostic org'), _('Editing diagnostic org')))
4032 if dlg.ShowModal() == wx.ID_OK:
4033 dlg.DestroyLater()
4034 return True
4035 dlg.DestroyLater()
4036 return False
4037 #----------------------------------------------------------------
4039
4040 if parent is None:
4041 parent = wx.GetApp().GetTopWindow()
4042
4043 #------------------------------------------------------------
4044 def edit(org=None):
4045 return edit_measurement_org(parent = parent, org = org)
4046 #------------------------------------------------------------
4047 def refresh(lctrl):
4048 orgs = gmPathLab.get_test_orgs()
4049 lctrl.set_string_items ([
4050 (o['unit'], o['organization'], gmTools.coalesce(o['test_org_contact'], ''), gmTools.coalesce(o['comment'], ''), o['pk_test_org'])
4051 for o in orgs
4052 ])
4053 lctrl.set_data(orgs)
4054 #------------------------------------------------------------
4055 def delete(test_org):
4056 gmPathLab.delete_test_org(test_org = test_org['pk_test_org'])
4057 return True
4058 #------------------------------------------------------------
4059 if msg is None:
4060 msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n')
4061
4062 return gmListWidgets.get_choices_from_list (
4063 parent = parent,
4064 msg = msg,
4065 caption = _('Showing diagnostic orgs.'),
4066 columns = [_('Name'), _('Organization'), _('Contact'), _('Comment'), '#'],
4067 single_selection = True,
4068 refresh_callback = refresh,
4069 edit_callback = edit,
4070 new_callback = edit,
4071 delete_callback = delete
4072 )
4073
4074 #----------------------------------------------------------------
4075 from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl
4076
4077 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
4078
4080
4081 try:
4082 data = kwargs['org']
4083 del kwargs['org']
4084 except KeyError:
4085 data = None
4086
4087 wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl.__init__(self, *args, **kwargs)
4088 gmEditArea.cGenericEditAreaMixin.__init__(self)
4089
4090 self.mode = 'new'
4091 self.data = data
4092 if data is not None:
4093 self.mode = 'edit'
4094
4095 #self.__init_ui()
4096 #----------------------------------------------------------------
4097 # def __init_ui(self):
4098 # # adjust phrasewheels etc
4099 #----------------------------------------------------------------
4100 # generic Edit Area mixin API
4101 #----------------------------------------------------------------
4103 has_errors = False
4104 if self._PRW_org_unit.GetData() is None:
4105 if self._PRW_org_unit.GetValue().strip() == '':
4106 has_errors = True
4107 self._PRW_org_unit.display_as_valid(valid = False)
4108 else:
4109 self._PRW_org_unit.display_as_valid(valid = True)
4110 else:
4111 self._PRW_org_unit.display_as_valid(valid = True)
4112
4113 return (not has_errors)
4114 #----------------------------------------------------------------
4116 data = gmPathLab.create_test_org (
4117 name = self._PRW_org_unit.GetValue().strip(),
4118 comment = self._TCTRL_comment.GetValue().strip(),
4119 pk_org_unit = self._PRW_org_unit.GetData()
4120 )
4121 data['test_org_contact'] = self._TCTRL_contact.GetValue().strip()
4122 data.save()
4123 self.data = data
4124 return True
4125 #----------------------------------------------------------------
4127 # get or create the org unit
4128 name = self._PRW_org_unit.GetValue().strip()
4129 org = gmOrganization.org_exists(organization = name)
4130 if org is None:
4131 org = gmOrganization.create_org (
4132 organization = name,
4133 category = 'Laboratory'
4134 )
4135 org_unit = gmOrganization.create_org_unit (
4136 pk_organization = org['pk_org'],
4137 unit = name
4138 )
4139 # update test_org fields
4140 self.data['pk_org_unit'] = org_unit['pk_org_unit']
4141 self.data['test_org_contact'] = self._TCTRL_contact.GetValue().strip()
4142 self.data['comment'] = self._TCTRL_comment.GetValue().strip()
4143 self.data.save()
4144 return True
4145 #----------------------------------------------------------------
4147 self._PRW_org_unit.SetText(value = '', data = None)
4148 self._TCTRL_contact.SetValue('')
4149 self._TCTRL_comment.SetValue('')
4150 #----------------------------------------------------------------
4152 self._PRW_org_unit.SetText(value = self.data['unit'], data = self.data['pk_org_unit'])
4153 self._TCTRL_contact.SetValue(gmTools.coalesce(self.data['test_org_contact'], ''))
4154 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
4155 #----------------------------------------------------------------
4158 #----------------------------------------------------------------
4161
4162 #----------------------------------------------------------------
4164
4166
4167 query = """
4168 SELECT DISTINCT ON (list_label)
4169 pk_test_org AS data,
4170 unit || ' (' || organization || ')' AS field_label,
4171 unit || ' @ ' || organization AS list_label
4172 FROM clin.v_test_orgs
4173 WHERE
4174 unit %(fragment_condition)s
4175 OR
4176 organization %(fragment_condition)s
4177 ORDER BY list_label
4178 LIMIT 50"""
4179 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4180 mp.setThresholds(1, 2, 4)
4181 #mp.word_separators = '[ \t:@]+'
4182 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4183 self.matcher = mp
4184 self.SetToolTip(_('The name of the path lab/diagnostic organisation.'))
4185 self.selection_only = False
4186 #------------------------------------------------------------
4188 if self.GetData() is not None:
4189 _log.debug('data already set, not creating')
4190 return
4191
4192 if self.GetValue().strip() == '':
4193 _log.debug('cannot create new lab, missing name')
4194 return
4195
4196 lab = gmPathLab.create_test_org(name = self.GetValue().strip())
4197 self.SetText(value = lab['unit'], data = lab['pk_test_org'])
4198 return
4199 #------------------------------------------------------------
4202
4203 #================================================================
4204 # Meta test type widgets
4205 #----------------------------------------------------------------
4207 ea = cMetaTestTypeEAPnl(parent, -1)
4208 ea.data = meta_test_type
4209 ea.mode = gmTools.coalesce(meta_test_type, 'new', 'edit')
4210 dlg = gmEditArea.cGenericEditAreaDlg2 (
4211 parent = parent,
4212 id = -1,
4213 edit_area = ea,
4214 single_entry = gmTools.bool2subst((meta_test_type is None), False, True)
4215 )
4216 dlg.SetTitle(gmTools.coalesce(meta_test_type, _('Adding new meta test type'), _('Editing meta test type')))
4217 if dlg.ShowModal() == wx.ID_OK:
4218 dlg.DestroyLater()
4219 return True
4220 dlg.DestroyLater()
4221 return False
4222
4223 #----------------------------------------------------------------
4225
4226 if parent is None:
4227 parent = wx.GetApp().GetTopWindow()
4228
4229 #------------------------------------------------------------
4230 def edit(meta_test_type=None):
4231 return edit_meta_test_type(parent = parent, meta_test_type = meta_test_type)
4232 #------------------------------------------------------------
4233 def delete(meta_test_type):
4234 gmPathLab.delete_meta_type(meta_type = meta_test_type['pk'])
4235 return True
4236 #----------------------------------------
4237 def get_tooltip(data):
4238 if data is None:
4239 return None
4240 return data.format(with_tests = True)
4241 #------------------------------------------------------------
4242 def refresh(lctrl):
4243 mtts = gmPathLab.get_meta_test_types()
4244 items = [ [
4245 m['abbrev'],
4246 m['name'],
4247 gmTools.coalesce(m['loinc'], ''),
4248 gmTools.coalesce(m['comment'], ''),
4249 m['pk']
4250 ] for m in mtts ]
4251 lctrl.set_string_items(items)
4252 lctrl.set_data(mtts)
4253 #----------------------------------------
4254
4255 msg = _(
4256 '\n'
4257 'These are the meta test types currently defined in GNUmed.\n'
4258 '\n'
4259 'Meta test types allow you to aggregate several actual test types used\n'
4260 'by pathology labs into one logical type.\n'
4261 '\n'
4262 'This is useful for grouping together results of tests which come under\n'
4263 'different names but really are the same thing. This often happens when\n'
4264 'you switch labs or the lab starts using another test method.\n'
4265 )
4266
4267 gmListWidgets.get_choices_from_list (
4268 parent = parent,
4269 msg = msg,
4270 caption = _('Showing meta test types.'),
4271 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), '#'],
4272 single_selection = True,
4273 list_tooltip_callback = get_tooltip,
4274 edit_callback = edit,
4275 new_callback = edit,
4276 delete_callback = delete,
4277 refresh_callback = refresh
4278 )
4279
4280 #----------------------------------------------------------------
4282
4284
4285 query = """
4286 SELECT DISTINCT ON (field_label)
4287 c_mtt.pk
4288 AS data,
4289 c_mtt.abbrev || ': ' || name
4290 AS field_label,
4291 c_mtt.abbrev || ': ' || name
4292 || coalesce (
4293 ' (' || c_mtt.comment || ')',
4294 ''
4295 )
4296 || coalesce (
4297 ', LOINC: ' || c_mtt.loinc,
4298 ''
4299 )
4300 AS list_label
4301 FROM
4302 clin.meta_test_type c_mtt
4303 WHERE
4304 abbrev %(fragment_condition)s
4305 OR
4306 name %(fragment_condition)s
4307 OR
4308 loinc %(fragment_condition)s
4309 ORDER BY field_label
4310 LIMIT 50"""
4311
4312 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4313 mp.setThresholds(1, 2, 4)
4314 mp.word_separators = '[ \t:@]+'
4315 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4316 self.matcher = mp
4317 self.SetToolTip(_('Select the meta test type.'))
4318 self.selection_only = True
4319 #------------------------------------------------------------
4321 if self.GetData() is None:
4322 return None
4323
4324 return gmPathLab.cMetaTestType(aPK_obj = self.GetData())
4325
4326 #----------------------------------------------------------------
4327 from Gnumed.wxGladeWidgets import wxgMetaTestTypeEAPnl
4328
4329 -class cMetaTestTypeEAPnl(wxgMetaTestTypeEAPnl.wxgMetaTestTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
4330
4332
4333 try:
4334 data = kwargs['meta_test_type']
4335 del kwargs['meta_test_type']
4336 except KeyError:
4337 data = None
4338
4339 wxgMetaTestTypeEAPnl.wxgMetaTestTypeEAPnl.__init__(self, *args, **kwargs)
4340 gmEditArea.cGenericEditAreaMixin.__init__(self)
4341
4342 # Code using this mixin should set mode and data
4343 # after instantiating the class:
4344 self.mode = 'new'
4345 self.data = data
4346 if data is not None:
4347 self.mode = 'edit'
4348
4349 self.__init_ui()
4350 #----------------------------------------------------------------
4352 # loinc
4353 mp = gmLOINC.cLOINCMatchProvider()
4354 mp.setThresholds(1, 2, 4)
4355 #mp.print_queries = True
4356 #mp.word_separators = '[ \t:@]+'
4357 self._PRW_loinc.matcher = mp
4358 self._PRW_loinc.selection_only = False
4359 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
4360
4361 #----------------------------------------------------------------
4362 # generic Edit Area mixin API
4363 #----------------------------------------------------------------
4365
4366 validity = True
4367
4368 if self._PRW_abbreviation.GetValue().strip() == '':
4369 validity = False
4370 self._PRW_abbreviation.display_as_valid(False)
4371 self.StatusText = _('Missing abbreviation for meta test type.')
4372 self._PRW_abbreviation.SetFocus()
4373 else:
4374 self._PRW_abbreviation.display_as_valid(True)
4375
4376 if self._PRW_name.GetValue().strip() == '':
4377 validity = False
4378 self._PRW_name.display_as_valid(False)
4379 self.StatusText = _('Missing name for meta test type.')
4380 self._PRW_name.SetFocus()
4381 else:
4382 self._PRW_name.display_as_valid(True)
4383
4384 return validity
4385 #----------------------------------------------------------------
4387
4388 # save the data as a new instance
4389 data = gmPathLab.create_meta_type (
4390 name = self._PRW_name.GetValue().strip(),
4391 abbreviation = self._PRW_abbreviation.GetValue().strip(),
4392 return_existing = False
4393 )
4394 if data is None:
4395 self.StatusText = _('This meta test type already exists.')
4396 return False
4397 data['loinc'] = self._PRW_loinc.GetData()
4398 data['comment'] = self._TCTRL_comment.GetValue().strip()
4399 data.save()
4400 self.data = data
4401 return True
4402 #----------------------------------------------------------------
4404 self.data['name'] = self._PRW_name.GetValue().strip()
4405 self.data['abbrev'] = self._PRW_abbreviation.GetValue().strip()
4406 self.data['loinc'] = self._PRW_loinc.GetData()
4407 self.data['comment'] = self._TCTRL_comment.GetValue().strip()
4408 self.data.save()
4409 return True
4410 #----------------------------------------------------------------
4412 self._PRW_name.SetText('', None)
4413 self._PRW_abbreviation.SetText('', None)
4414 self._PRW_loinc.SetText('', None)
4415 self._TCTRL_loinc_info.SetValue('')
4416 self._TCTRL_comment.SetValue('')
4417 self._LBL_member_detail.SetLabel('')
4418
4419 self._PRW_name.SetFocus()
4420 #----------------------------------------------------------------
4423 #----------------------------------------------------------------
4425 self._PRW_name.SetText(self.data['name'], self.data['pk'])
4426 self._PRW_abbreviation.SetText(self.data['abbrev'], self.data['abbrev'])
4427 self._PRW_loinc.SetText(gmTools.coalesce(self.data['loinc'], ''), self.data['loinc'])
4428 self.__refresh_loinc_info()
4429 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
4430 self.__refresh_members()
4431
4432 self._PRW_name.SetFocus()
4433 #----------------------------------------------------------------
4434 # event handlers
4435 #----------------------------------------------------------------
4438 #----------------------------------------------------------------
4439 # internal helpers
4440 #----------------------------------------------------------------
4442 loinc = self._PRW_loinc.GetData()
4443
4444 if loinc is None:
4445 self._TCTRL_loinc_info.SetValue('')
4446 return
4447
4448 info = gmLOINC.loinc2term(loinc = loinc)
4449 if len(info) == 0:
4450 self._TCTRL_loinc_info.SetValue('')
4451 return
4452
4453 self._TCTRL_loinc_info.SetValue(info[0])
4454 #----------------------------------------------------------------
4456 if self.data is None:
4457 self._LBL_member_detail.SetLabel('')
4458 return
4459
4460 types = self.data.included_test_types
4461 if len(types) == 0:
4462 self._LBL_member_detail.SetLabel('')
4463 return
4464
4465 lines = []
4466 for tt in types:
4467 lines.append('%s (%s%s) [#%s] @ %s' % (
4468 tt['name'],
4469 tt['abbrev'],
4470 gmTools.coalesce(tt['loinc'], '', ', LOINC: %s'),
4471 tt['pk_test_type'],
4472 tt['name_org']
4473 ))
4474 self._LBL_member_detail.SetLabel('\n'.join(lines))
4475
4476 #================================================================
4477 # test panel handling
4478 #================================================================
4480 ea = cTestPanelEAPnl(parent, -1)
4481 ea.data = test_panel
4482 ea.mode = gmTools.coalesce(test_panel, 'new', 'edit')
4483 dlg = gmEditArea.cGenericEditAreaDlg2 (
4484 parent = parent,
4485 id = -1,
4486 edit_area = ea,
4487 single_entry = gmTools.bool2subst((test_panel is None), False, True)
4488 )
4489 dlg.SetTitle(gmTools.coalesce(test_panel, _('Adding new test panel'), _('Editing test panel')))
4490 if dlg.ShowModal() == wx.ID_OK:
4491 dlg.DestroyLater()
4492 return True
4493 dlg.DestroyLater()
4494 return False
4495
4496 #----------------------------------------------------------------
4498
4499 if parent is None:
4500 parent = wx.GetApp().GetTopWindow()
4501
4502 #------------------------------------------------------------
4503 def edit(test_panel=None):
4504 return edit_test_panel(parent = parent, test_panel = test_panel)
4505 #------------------------------------------------------------
4506 def delete(test_panel):
4507 gmPathLab.delete_test_panel(pk = test_panel['pk_test_panel'])
4508 return True
4509 #------------------------------------------------------------
4510 def get_tooltip(test_panel):
4511 return test_panel.format()
4512 #------------------------------------------------------------
4513 def refresh(lctrl):
4514 panels = gmPathLab.get_test_panels(order_by = 'description')
4515 items = [ [
4516 p['description'],
4517 gmTools.coalesce(p['comment'], ''),
4518 p['pk_test_panel']
4519 ] for p in panels ]
4520 lctrl.set_string_items(items)
4521 lctrl.set_data(panels)
4522 #------------------------------------------------------------
4523 gmListWidgets.get_choices_from_list (
4524 parent = parent,
4525 caption = 'GNUmed: ' + _('Test panels list'),
4526 columns = [ _('Name'), _('Comment'), '#' ],
4527 single_selection = True,
4528 refresh_callback = refresh,
4529 edit_callback = edit,
4530 new_callback = edit,
4531 delete_callback = delete,
4532 list_tooltip_callback = get_tooltip
4533 )
4534
4535 #----------------------------------------------------------------
4537
4539 query = """
4540 SELECT
4541 pk_test_panel
4542 AS data,
4543 description
4544 AS field_label,
4545 description
4546 AS list_label
4547 FROM
4548 clin.v_test_panels
4549 WHERE
4550 description %(fragment_condition)s
4551 ORDER BY field_label
4552 LIMIT 30"""
4553 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4554 mp.setThresholds(1, 2, 4)
4555 #mp.word_separators = '[ \t:@]+'
4556 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4557 self.matcher = mp
4558 self.SetToolTip(_('Select a test panel.'))
4559 self.selection_only = True
4560 #------------------------------------------------------------
4562 if self.GetData() is None:
4563 return None
4564 return gmPathLab.cTestPanel(aPK_obj = self.GetData())
4565 #------------------------------------------------------------
4567 if self.GetData() is None:
4568 return None
4569 return gmPathLab.cTestPanel(aPK_obj = self.GetData()).format()
4570
4571 #====================================================================
4572 from Gnumed.wxGladeWidgets import wxgTestPanelEAPnl
4573
4575
4577
4578 try:
4579 data = kwargs['panel']
4580 del kwargs['panel']
4581 except KeyError:
4582 data = None
4583
4584 wxgTestPanelEAPnl.wxgTestPanelEAPnl.__init__(self, *args, **kwargs)
4585 gmEditArea.cGenericEditAreaMixin.__init__(self)
4586
4587 self.__loincs = None
4588
4589 self.mode = 'new'
4590 self.data = data
4591 if data is not None:
4592 self.mode = 'edit'
4593
4594 self.__init_ui()
4595
4596 #----------------------------------------------------------------
4598 self._LCTRL_loincs.set_columns([_('LOINC'), _('Term'), _('Units')])
4599 self._LCTRL_loincs.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
4600 #self._LCTRL_loincs.set_resize_column(column = 2)
4601 self._LCTRL_loincs.delete_callback = self._remove_loincs_from_list
4602 self.__refresh_loinc_list()
4603
4604 self._PRW_loinc.final_regex = r'.*'
4605 self._PRW_loinc.add_callback_on_selection(callback = self._on_loinc_selected)
4606
4607 #----------------------------------------------------------------
4609 self._LCTRL_loincs.remove_items_safely()
4610 if self.__loincs is None:
4611 if self.data is None:
4612 return
4613 self.__loincs = self.data['loincs']
4614
4615 items = []
4616 for loinc in self.__loincs:
4617 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4618 if loinc_detail is None:
4619 # check for test type with this pseudo loinc
4620 ttypes = gmPathLab.get_measurement_types(loincs = [loinc])
4621 if len(ttypes) == 0:
4622 items.append([loinc, _('LOINC not found'), ''])
4623 else:
4624 for tt in ttypes:
4625 items.append([loinc, _('not a LOINC') + u'; %(name)s @ %(name_org)s [#%(pk_test_type)s]' % tt, ''])
4626 continue
4627 items.append ([
4628 loinc,
4629 loinc_detail['term'],
4630 gmTools.coalesce(loinc_detail['example_units'], '', '%s')
4631 ])
4632
4633 self._LCTRL_loincs.set_string_items(items)
4634 self._LCTRL_loincs.set_column_widths()
4635
4636 #----------------------------------------------------------------
4637 # generic Edit Area mixin API
4638 #----------------------------------------------------------------
4640 validity = True
4641
4642 if self.__loincs is None:
4643 if self.data is not None:
4644 self.__loincs = self.data['loincs']
4645
4646 if self.__loincs is None:
4647 # not fatal despite panel being useless
4648 self.StatusText = _('No LOINC codes selected.')
4649 self._PRW_loinc.SetFocus()
4650
4651 if self._TCTRL_description.GetValue().strip() == '':
4652 validity = False
4653 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False)
4654 self._TCTRL_description.SetFocus()
4655 else:
4656 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True)
4657
4658 return validity
4659
4660 #----------------------------------------------------------------
4662 data = gmPathLab.create_test_panel(description = self._TCTRL_description.GetValue().strip())
4663 data['comment'] = self._TCTRL_comment.GetValue().strip()
4664 data.save()
4665 if self.__loincs is not None:
4666 data.included_loincs = self.__loincs
4667 self.data = data
4668 return True
4669
4670 #----------------------------------------------------------------
4672 self.data['description'] = self._TCTRL_description.GetValue().strip()
4673 self.data['comment'] = self._TCTRL_comment.GetValue().strip()
4674 self.data.save()
4675 if self.__loincs is not None:
4676 self.data.included_loincs = self.__loincs
4677 return True
4678
4679 #----------------------------------------------------------------
4681 self._TCTRL_description.SetValue('')
4682 self._TCTRL_comment.SetValue('')
4683 self._PRW_loinc.SetText('', None)
4684 self._LBL_loinc.SetLabel('')
4685 self.__loincs = None
4686 self.__refresh_loinc_list()
4687
4688 self._TCTRL_description.SetFocus()
4689
4690 #----------------------------------------------------------------
4693
4694 #----------------------------------------------------------------
4696 self._TCTRL_description.SetValue(self.data['description'])
4697 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
4698 self._PRW_loinc.SetText('', None)
4699 self._LBL_loinc.SetLabel('')
4700 self.__loincs = self.data['loincs']
4701 self.__refresh_loinc_list()
4702
4703 self._PRW_loinc.SetFocus()
4704
4705 #----------------------------------------------------------------
4706 # event handlers
4707 #----------------------------------------------------------------
4709 loinc = self._PRW_loinc.GetData()
4710 if loinc is None:
4711 self._LBL_loinc.SetLabel('')
4712 return
4713 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4714 if loinc_detail is None:
4715 loinc_str = _('no LOINC details found')
4716 else:
4717 loinc_str = '%s: %s%s' % (
4718 loinc,
4719 loinc_detail['term'],
4720 gmTools.coalesce(loinc_detail['example_units'], '', ' (%s)')
4721 )
4722 self._LBL_loinc.SetLabel(loinc_str)
4723
4724 #----------------------------------------------------------------
4746
4747 #----------------------------------------------------------------
4751
4752 #----------------------------------------------------------------
4754 loincs2remove = self._LCTRL_loincs.selected_item_data
4755 if loincs2remove is None:
4756 return
4757 for loinc in loincs2remove:
4758 try:
4759 while True:
4760 self.__loincs.remove(loinc[0])
4761 except ValueError:
4762 pass
4763 self.__refresh_loinc_list()
4764
4765 #================================================================
4766 # main
4767 #----------------------------------------------------------------
4768 if __name__ == '__main__':
4769
4770 from Gnumed.pycommon import gmLog2
4771 from Gnumed.wxpython import gmPatSearchWidgets
4772
4773 gmI18N.activate_locale()
4774 gmI18N.install_domain()
4775 gmDateTime.init()
4776
4777 #------------------------------------------------------------
4779 pat = gmPersonSearch.ask_for_patient()
4780 app = wx.PyWidgetTester(size = (500, 300))
4781 lab_grid = cMeasurementsGrid(app.frame, -1)
4782 lab_grid.patient = pat
4783 app.frame.Show()
4784 app.MainLoop()
4785 #------------------------------------------------------------
4787 pat = gmPersonSearch.ask_for_patient()
4788 gmPatSearchWidgets.set_active_patient(patient=pat)
4789 app = wx.PyWidgetTester(size = (500, 300))
4790 ea = cMeasurementEditAreaPnl(app.frame, -1)
4791 app.frame.Show()
4792 app.MainLoop()
4793 #------------------------------------------------------------
4794 # def test_primary_care_vitals_pnl():
4795 # app = wx.PyWidgetTester(size = (500, 300))
4796 # pnl = wxgPrimaryCareVitalsInputPnl.wxgPrimaryCareVitalsInputPnl(app.frame, -1)
4797 # app.frame.Show()
4798 # app.MainLoop()
4799 #------------------------------------------------------------
4800 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
4801 #test_grid()
4802 test_test_ea_pnl()
4803 #test_primary_care_vitals_pnl()
4804
4805 #================================================================
4806
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sun Jul 28 01:55:29 2019 | http://epydoc.sourceforge.net |