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