| 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.edit_callback = self._on_edit
924 self._PNL_related_documents.lab_reference = None
925
926 #------------------------------------------------------------
929
930 #------------------------------------------------------------
932 self._LCTRL_days.set_string_items()
933 self._LCTRL_results.set_string_items()
934 self._TCTRL_measurements.SetValue('')
935 self._PNL_related_documents.lab_reference = None
936
937 #------------------------------------------------------------
939 if self.__patient is None:
940 self.__clear()
941 return
942
943 dates = self.__patient.emr.get_dates_for_results(reverse_chronological = True)
944 items = [ ['%s%s' % (
945 gmDateTime.pydt_strftime(d['clin_when_day'], self.__date_format),
946 gmTools.bool2subst(d['is_reviewed'], '', gmTools.u_writing_hand, gmTools.u_writing_hand)
947 )]
948 for d in dates
949 ]
950
951 self._LCTRL_days.set_string_items(items)
952 self._LCTRL_days.set_data(dates)
953 if len(items) > 0:
954 self._LCTRL_days.Select(idx = 0, on = 1)
955 self._LCTRL_days.SetFocus()
956
957 #------------------------------------------------------------
959 item_data = self._LCTRL_results.get_selected_item_data(only_one = True)
960 if item_data is None:
961 return
962 if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True):
963 self.__repopulate_ui()
964
965 #------------------------------------------------------------
966 # event handlers
967 #------------------------------------------------------------
969 if self.__patient is None:
970 return True
971
972 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet
973 if kwds['pk_identity'] != self.__patient.ID:
974 return True
975
976 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
977 return True
978
979 self._schedule_data_reget()
980 return True
981
982 #------------------------------------------------------------
984 event.Skip()
985
986 day = self._LCTRL_days.get_item_data(item_idx = event.Index)['clin_when_day']
987 results = self.__patient.emr.get_results_for_day(timestamp = day)
988 items = []
989 data = []
990 for r in results:
991 range_info = gmTools.coalesce (
992 r.formatted_clinical_range,
993 r.formatted_normal_range
994 )
995 review = gmTools.bool2subst (
996 r['reviewed'],
997 '',
998 ' ' + gmTools.u_writing_hand,
999 ' ' + gmTools.u_writing_hand
1000 )
1001 items.append ([
1002 gmDateTime.pydt_strftime(r['clin_when'], '%H:%M'),
1003 r['abbrev_tt'],
1004 '%s%s%s%s' % (
1005 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1006 gmTools.coalesce(r['val_unit'], '', ' %s'),
1007 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1008 review
1009 ),
1010 gmTools.coalesce(range_info, '')
1011 ])
1012 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
1013
1014 self._LCTRL_results.set_string_items(items)
1015 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1016 self._LCTRL_results.set_data(data)
1017 self._LCTRL_results.Select(idx = 0, on = 1)
1018
1019 #------------------------------------------------------------
1021 event.Skip()
1022 item_data = self._LCTRL_results.get_item_data(item_idx = event.Index)
1023 self._TCTRL_measurements.SetValue(item_data['formatted'])
1024 self._PNL_related_documents.lab_reference = item_data['data']
1025
1026 #------------------------------------------------------------
1027 # reget mixin API
1028 #------------------------------------------------------------
1032
1033 #------------------------------------------------------------
1034 # properties
1035 #------------------------------------------------------------
1038
1040 if (self.__patient is None) and (patient is None):
1041 return
1042 if patient is None:
1043 self.__patient = None
1044 self.__clear()
1045 return
1046 if self.__patient is None:
1047 self.__patient = patient
1048 self._schedule_data_reget()
1049 return
1050 if self.__patient.ID == patient.ID:
1051 return
1052 self.__patient = patient
1053 self._schedule_data_reget()
1054
1055 patient = property(_get_patient, _set_patient)
1056
1057 #================================================================
1058 from Gnumed.wxGladeWidgets import wxgMeasurementsByIssuePnl
1059
1060 -class cMeasurementsByIssuePnl(wxgMeasurementsByIssuePnl.wxgMeasurementsByIssuePnl, gmRegetMixin.cRegetOnPaintMixin):
1061 """A class for displaying measurement results as a list partitioned by issue/episode.
1062
1063 - operates on a cPatient instance handed to it and NOT on the currently active patient
1064 """
1066 wxgMeasurementsByIssuePnl.wxgMeasurementsByIssuePnl.__init__(self, *args, **kwargs)
1067
1068 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1069
1070 self.__patient = None
1071
1072 self.__init_ui()
1073 self.__register_events()
1074
1075 #------------------------------------------------------------
1076 # internal helpers
1077 #------------------------------------------------------------
1079 self._LCTRL_issues.set_columns([_('Problem')])
1080 self._LCTRL_results.set_columns([_('When'), _('Test'), _('Result'), _('Reference')])
1081 self._PNL_related_documents.lab_reference = None
1082
1083 #------------------------------------------------------------
1085 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
1086 self._LCTRL_issues.select_callback = self._on_problem_selected
1087 self._LCTRL_results.edit_callback = self._on_edit
1088 self._LCTRL_results.select_callback = self._on_result_selected
1089
1090 #------------------------------------------------------------
1092 self._LCTRL_issues.set_string_items()
1093 self._LCTRL_results.set_string_items()
1094 self._TCTRL_measurements.SetValue('')
1095 self._PNL_related_documents.lab_reference = None
1096
1097 #------------------------------------------------------------
1099 if self.__patient is None:
1100 self.__clear()
1101 return
1102
1103 probs = self.__patient.emr.get_issues_or_episodes_for_results()
1104 items = [ ['%s%s' % (
1105 gmTools.coalesce (
1106 value2test = p['pk_health_issue'],
1107 value2return = '',
1108 return_instead = gmTools.u_diameter + ':'
1109 ),
1110 gmTools.shorten_words_in_line(text = p['problem'], min_word_length = 5, max_length = 30)
1111 )] for p in probs ]
1112 self._LCTRL_issues.set_string_items(items)
1113 self._LCTRL_issues.set_data([ {'pk_issue': p['pk_health_issue'], 'pk_episode': p['pk_episode']} for p in probs ])
1114 if len(items) > 0:
1115 self._LCTRL_issues.Select(idx = 0, on = 1)
1116 self._LCTRL_issues.SetFocus()
1117
1118 #------------------------------------------------------------
1120 item_data = self._LCTRL_results.get_selected_item_data(only_one = True)
1121 if item_data is None:
1122 return
1123 if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True):
1124 self.__repopulate_ui()
1125
1126 #------------------------------------------------------------
1127 # event handlers
1128 #------------------------------------------------------------
1130 if self.__patient is None:
1131 return True
1132
1133 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet
1134 if kwds['pk_identity'] != self.__patient.ID:
1135 return True
1136
1137 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1138 return True
1139
1140 self._schedule_data_reget()
1141 return True
1142
1143 #------------------------------------------------------------
1145 event.Skip()
1146
1147 pk_issue = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_issue']
1148 if pk_issue is None:
1149 pk_episode = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_episode']
1150 results = self.__patient.emr.get_results_for_episode(pk_episode = pk_episode)
1151 else:
1152 results = self.__patient.emr.get_results_for_issue(pk_health_issue = pk_issue)
1153 items = []
1154 data = []
1155 for r in results:
1156 range_info = gmTools.coalesce (
1157 r.formatted_clinical_range,
1158 r.formatted_normal_range
1159 )
1160 review = gmTools.bool2subst (
1161 r['reviewed'],
1162 '',
1163 ' ' + gmTools.u_writing_hand,
1164 ' ' + gmTools.u_writing_hand
1165 )
1166 items.append ([
1167 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M'),
1168 r['abbrev_tt'],
1169 '%s%s%s%s' % (
1170 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1171 gmTools.coalesce(r['val_unit'], '', ' %s'),
1172 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1173 review
1174 ),
1175 gmTools.coalesce(range_info, '')
1176 ])
1177 data.append({'data': r, 'formatted': r.format(with_source_data = True)})
1178
1179 self._LCTRL_results.set_string_items(items)
1180 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1181 self._LCTRL_results.set_data(data)
1182 self._LCTRL_results.Select(idx = 0, on = 1)
1183 self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted'])
1184
1185 #------------------------------------------------------------
1187 event.Skip()
1188 item_data = self._LCTRL_results.get_item_data(item_idx = event.Index)
1189 self._TCTRL_measurements.SetValue(item_data['formatted'])
1190 self._PNL_related_documents.lab_reference = item_data['data']
1191
1192 #------------------------------------------------------------
1193 # reget mixin API
1194 #------------------------------------------------------------
1198
1199 #------------------------------------------------------------
1200 # properties
1201 #------------------------------------------------------------
1204
1206 if (self.__patient is None) and (patient is None):
1207 return
1208 if patient is None:
1209 self.__patient = None
1210 self.__clear()
1211 return
1212 if self.__patient is None:
1213 self.__patient = patient
1214 self._schedule_data_reget()
1215 return
1216 if self.__patient.ID == patient.ID:
1217 return
1218 self.__patient = patient
1219 self._schedule_data_reget()
1220
1221 patient = property(_get_patient, _set_patient)
1222
1223 #================================================================
1224 from Gnumed.wxGladeWidgets import wxgMeasurementsByBatteryPnl
1225
1226 -class cMeasurementsByBatteryPnl(wxgMeasurementsByBatteryPnl.wxgMeasurementsByBatteryPnl, gmRegetMixin.cRegetOnPaintMixin):
1227 """A grid class for displaying measurement results filtered by battery/panel.
1228
1229 - operates on a cPatient instance handed to it and NOT on the currently active patient
1230 """
1232 wxgMeasurementsByBatteryPnl.wxgMeasurementsByBatteryPnl.__init__(self, *args, **kwargs)
1233
1234 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1235
1236 self.__patient = None
1237
1238 self.__init_ui()
1239 self.__register_events()
1240
1241 #------------------------------------------------------------
1242 # internal helpers
1243 #------------------------------------------------------------
1245 self._GRID_results_battery.show_by_panel = True
1246
1247 #------------------------------------------------------------
1249 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
1250
1251 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
1252 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
1253
1254 #------------------------------------------------------------
1258
1259 #--------------------------------------------------------
1261 if panel is None:
1262 self._TCTRL_panel_comment.SetValue('')
1263 self._GRID_results_battery.panel_to_show = None
1264 else:
1265 pnl = self._PRW_panel.GetData(as_instance = True)
1266 self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
1267 pnl['comment'],
1268 ''
1269 ))
1270 self._GRID_results_battery.panel_to_show = pnl
1271 # self.Layout()
1272
1273 #--------------------------------------------------------
1275 self._TCTRL_panel_comment.SetValue('')
1276 if self._PRW_panel.GetValue().strip() == '':
1277 self._GRID_results_battery.panel_to_show = None
1278 # self.Layout()
1279
1280 #------------------------------------------------------------
1281 # event handlers
1282 #------------------------------------------------------------
1284 if self.__patient is None:
1285 return True
1286
1287 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet
1288 if kwds['pk_identity'] != self.__patient.ID:
1289 return True
1290
1291 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1292 return True
1293
1294 self._schedule_data_reget()
1295 return True
1296
1297 #------------------------------------------------------------
1300
1301 #--------------------------------------------------------
1304
1305 #--------------------------------------------------------
1308
1309 #------------------------------------------------------------
1310 # reget mixin API
1311 #------------------------------------------------------------
1315
1316 #------------------------------------------------------------
1317 # properties
1318 #------------------------------------------------------------
1321
1323 if (self.__patient is None) and (patient is None):
1324 return
1325 if (self.__patient is None) or (patient is None):
1326 self.__patient = patient
1327 self._schedule_data_reget()
1328 return
1329 if self.__patient.ID == patient.ID:
1330 return
1331 self.__patient = patient
1332 self._schedule_data_reget()
1333
1334 patient = property(_get_patient, _set_patient)
1335
1336 #================================================================
1337 from Gnumed.wxGladeWidgets import wxgMeasurementsAsMostRecentListPnl
1338
1339 -class cMeasurementsAsMostRecentListPnl(wxgMeasurementsAsMostRecentListPnl.wxgMeasurementsAsMostRecentListPnl, gmRegetMixin.cRegetOnPaintMixin):
1340 """A list ctrl class for displaying measurement results.
1341
1342 - most recent results
1343 - possibly filtered by battery/panel
1344
1345 - operates on a cPatient instance handed to it and NOT on the currently active patient
1346 """
1348 wxgMeasurementsAsMostRecentListPnl.wxgMeasurementsAsMostRecentListPnl.__init__(self, *args, **kwargs)
1349
1350 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1351
1352 self.__patient = None
1353
1354 self.__init_ui()
1355 self.__register_events()
1356
1357 #------------------------------------------------------------
1358 # internal helpers
1359 #------------------------------------------------------------
1361 self._LCTRL_results.set_columns([_('Test'), _('Result'), _('When'), _('Range')])
1362 self._CHBOX_show_missing.Disable()
1363 self._PNL_related_documents.lab_reference = None
1364
1365 #------------------------------------------------------------
1367 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
1368
1369 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
1370 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
1371
1372 self._LCTRL_results.select_callback = self._on_result_selected
1373 self._LCTRL_results.edit_callback = self._on_edit
1374
1375 #------------------------------------------------------------
1377
1378 self._TCTRL_details.SetValue('')
1379 self._PNL_related_documents.lab_reference = None
1380 if self.__patient is None:
1381 self._LCTRL_results.remove_items_safely()
1382 return
1383
1384 pnl = self._PRW_panel.GetData(as_instance = True)
1385 if pnl is None:
1386 results = gmPathLab.get_most_recent_result_for_test_types (
1387 pk_patient = self.__patient.ID,
1388 consider_meta_type = True
1389 )
1390 else:
1391 results = pnl.get_most_recent_results (
1392 pk_patient = self.__patient.ID,
1393 #order_by = ,
1394 group_by_meta_type = True,
1395 include_missing = self._CHBOX_show_missing.IsChecked()
1396 )
1397 items = []
1398 data = []
1399 for r in results:
1400 if isinstance(r, gmPathLab.cTestResult):
1401 result_type = gmTools.coalesce (
1402 value2test = r['pk_meta_test_type'],
1403 return_instead = r['abbrev_tt'],
1404 value2return = '%s%s' % (gmTools.u_sum, r['abbrev_meta'])
1405 )
1406 review = gmTools.bool2subst (
1407 r['reviewed'],
1408 '',
1409 ' ' + gmTools.u_writing_hand,
1410 ' ' + gmTools.u_writing_hand
1411 )
1412 result_val = '%s%s%s%s' % (
1413 gmTools.strip_empty_lines(text = r['unified_val'])[0],
1414 gmTools.coalesce(r['val_unit'], '', ' %s'),
1415 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'),
1416 review
1417 )
1418 result_when = _('%s ago (%s)') % (
1419 gmDateTime.format_interval_medically(interval = gmDateTime.pydt_now_here() - r['clin_when']),
1420 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes)
1421 )
1422 range_info = gmTools.coalesce (
1423 r.formatted_clinical_range,
1424 r.formatted_normal_range
1425 )
1426 tt = r.format(with_source_data = True)
1427 else:
1428 result_type = r
1429 result_val = _('missing')
1430 loinc_data = gmLOINC.loinc2data(r)
1431 if loinc_data is None:
1432 result_when = _('LOINC not found')
1433 tt = u''
1434 else:
1435 result_when = loinc_data['term']
1436 tt = gmLOINC.format_loinc(r)
1437 range_info = None
1438 items.append([result_type, result_val, result_when, gmTools.coalesce(range_info, '')])
1439 data.append({'data': r, 'formatted': tt})
1440
1441 self._LCTRL_results.set_string_items(items)
1442 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
1443 self._LCTRL_results.set_data(data)
1444
1445 if len(items) > 0:
1446 self._LCTRL_results.Select(idx = 0, on = 1)
1447 self._LCTRL_results.SetFocus()
1448
1449 return True
1450
1451 #--------------------------------------------------------
1453 if panel is None:
1454 self._TCTRL_panel_comment.SetValue('')
1455 self._CHBOX_show_missing.Disable()
1456 else:
1457 pnl = self._PRW_panel.GetData(as_instance = True)
1458 self._TCTRL_panel_comment.SetValue(gmTools.coalesce(pnl['comment'], ''))
1459 self.__repopulate_ui()
1460 self._CHBOX_show_missing.Enable()
1461
1462 #--------------------------------------------------------
1464 self._TCTRL_panel_comment.SetValue('')
1465 if self._PRW_panel.Value.strip() == u'':
1466 self.__repopulate_ui()
1467 self._CHBOX_show_missing.Disable()
1468
1469 #------------------------------------------------------------
1470 # event handlers
1471 #------------------------------------------------------------
1473 if self.__patient is None:
1474 return True
1475
1476 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet
1477 if kwds['pk_identity'] != self.__patient.ID:
1478 return True
1479
1480 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results', 'clin.test_panel']:
1481 return True
1482
1483 self._schedule_data_reget()
1484 return True
1485
1486 #------------------------------------------------------------
1489
1490 #--------------------------------------------------------
1493
1494 #--------------------------------------------------------
1497
1498 #------------------------------------------------------------
1500 event.Skip()
1501 item_data = self._LCTRL_results.get_item_data(item_idx = event.Index)
1502 self._TCTRL_details.SetValue(item_data['formatted'])
1503 if isinstance(item_data['data'], gmPathLab.cTestResult):
1504 self._PNL_related_documents.lab_reference = item_data['data']
1505 else:
1506 self._PNL_related_documents.lab_reference = None
1507
1508 #------------------------------------------------------------
1510 item_data = self._LCTRL_results.get_selected_item_data(only_one = True)
1511 if item_data is None:
1512 return
1513 if isinstance(item_data['data'], gmPathLab.cTestResult):
1514 if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True):
1515 self.__repopulate_ui()
1516
1517 #------------------------------------------------------------
1519 event.Skip()
1520 # should not happen
1521 if self._PRW_panel.GetData(as_instance = False) is None:
1522 return
1523 self.__repopulate_ui()
1524
1525 #------------------------------------------------------------
1526 # reget mixin API
1527 #------------------------------------------------------------
1531
1532 #------------------------------------------------------------
1533 # properties
1534 #------------------------------------------------------------
1537
1539 if (self.__patient is None) and (patient is None):
1540 return
1541 if (self.__patient is None) or (patient is None):
1542 self.__patient = patient
1543 self._schedule_data_reget()
1544 return
1545 if self.__patient.ID == patient.ID:
1546 return
1547 self.__patient = patient
1548 self._schedule_data_reget()
1549
1550 patient = property(_get_patient, _set_patient)
1551
1552 #================================================================
1553 from Gnumed.wxGladeWidgets import wxgMeasurementsAsTablePnl
1554
1555 -class cMeasurementsAsTablePnl(wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl, gmRegetMixin.cRegetOnPaintMixin):
1556 """A panel for holding a grid displaying all measurement results.
1557
1558 - operates on a cPatient instance handed to it and NOT on the currently active patient
1559 """
1561 wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl.__init__(self, *args, **kwargs)
1562
1563 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1564
1565 self.__patient = None
1566
1567 self.__init_ui()
1568 self.__register_events()
1569
1570 #------------------------------------------------------------
1571 # internal helpers
1572 #------------------------------------------------------------
1574 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
1575
1576 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
1577 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
1578
1579 item = self.__action_button_popup.Append(-1, _('Plot'))
1580 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
1581
1582 #item = self.__action_button_popup.Append(-1, _('Export to &file'))
1583 #self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item)
1584 #self.__action_button_popup.Enable(id = item.Id, enable = False)
1585
1586 #item = self.__action_button_popup.Append(-1, _('Export to &clipboard'))
1587 #self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item)
1588 #self.__action_button_popup.Enable(id = item.Id, enable = False)
1589
1590 item = self.__action_button_popup.Append(-1, _('&Delete'))
1591 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
1592
1593 # FIXME: create inbox message to staff to phone patient to come in
1594 # FIXME: generate and let edit a SOAP narrative and include the values
1595
1596 self._GRID_results_all.show_by_panel = False
1597
1598 #------------------------------------------------------------
1601
1602 #------------------------------------------------------------
1604 self._GRID_results_all.patient = self.__patient
1605 #self._GRID_results_battery.Fit()
1606 self.Layout()
1607 return True
1608
1609 #------------------------------------------------------------
1611 self._GRID_results_all.sign_current_selection()
1612
1613 #------------------------------------------------------------
1615 self._GRID_results_all.plot_current_selection()
1616
1617 #------------------------------------------------------------
1619 self._GRID_results_all.delete_current_selection()
1620
1621 #------------------------------------------------------------
1622 # event handlers
1623 #------------------------------------------------------------
1625 if self.__patient is None:
1626 return True
1627
1628 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet
1629 if kwds['pk_identity'] != self.__patient.ID:
1630 return True
1631
1632 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']:
1633 return True
1634
1635 self._schedule_data_reget()
1636 return True
1637
1638 #--------------------------------------------------------
1641
1642 #--------------------------------------------------------
1646
1647 #--------------------------------------------------------
1650
1651 #--------------------------------------------------------
1657
1658 #------------------------------------------------------------
1659 # reget mixin API
1660 #------------------------------------------------------------
1664
1665 #------------------------------------------------------------
1666 # properties
1667 #------------------------------------------------------------
1670
1672 if (self.__patient is None) and (patient is None):
1673 return
1674 if (self.__patient is None) or (patient is None):
1675 self.__patient = patient
1676 self._schedule_data_reget()
1677 return
1678 if self.__patient.ID == patient.ID:
1679 return
1680 self.__patient = patient
1681 self._schedule_data_reget()
1682
1683 patient = property(_get_patient, _set_patient)
1684
1685 #================================================================
1686 # notebook based measurements plugin
1687 #================================================================
1689 """Notebook displaying measurements pages:
1690
1691 - by test battery
1692 - by day
1693 - by issue/episode
1694 - most-recent list, perhaps by panel
1695 - full grid
1696 - full list
1697
1698 Used as a main notebook plugin page.
1699
1700 Operates on the active patient.
1701 """
1702 #--------------------------------------------------------
1704
1705 wx.Notebook.__init__ (
1706 self,
1707 parent = parent,
1708 id = id,
1709 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
1710 name = self.__class__.__name__
1711 )
1712 _log.debug('created wx.Notebook: %s with ID %s', self.__class__.__name__, self.Id)
1713 gmPlugin.cPatientChange_PluginMixin.__init__(self)
1714 self.__patient = gmPerson.gmCurrentPatient()
1715 self.__init_ui()
1716 self.SetSelection(0)
1717
1718 #--------------------------------------------------------
1719 # patient change plugin API
1720 #--------------------------------------------------------
1722 for page_idx in range(self.GetPageCount()):
1723 page = self.GetPage(page_idx)
1724 page.patient = None
1725
1726 #--------------------------------------------------------
1728 for page_idx in range(self.GetPageCount()):
1729 page = self.GetPage(page_idx)
1730 page.patient = self.__patient.patient
1731
1732 #--------------------------------------------------------
1733 # notebook plugin API
1734 #--------------------------------------------------------
1736 if self.__patient.connected:
1737 pat = self.__patient.patient
1738 else:
1739 pat = None
1740 for page_idx in range(self.GetPageCount()):
1741 page = self.GetPage(page_idx)
1742 page.patient = pat
1743
1744 return True
1745
1746 #--------------------------------------------------------
1747 # internal API
1748 #--------------------------------------------------------
1750
1751 # by day
1752 new_page = cMeasurementsByDayPnl(self, -1)
1753 new_page.patient = None
1754 self.AddPage (
1755 page = new_page,
1756 text = _('Days'),
1757 select = True
1758 )
1759
1760 # by issue
1761 new_page = cMeasurementsByIssuePnl(self, -1)
1762 new_page.patient = None
1763 self.AddPage (
1764 page = new_page,
1765 text = _('Problems'),
1766 select = False
1767 )
1768
1769 # by test panel
1770 new_page = cMeasurementsByBatteryPnl(self, -1)
1771 new_page.patient = None
1772 self.AddPage (
1773 page = new_page,
1774 text = _('Panels'),
1775 select = False
1776 )
1777
1778 # most-recent, by panel
1779 new_page = cMeasurementsAsMostRecentListPnl(self, -1)
1780 new_page.patient = None
1781 self.AddPage (
1782 page = new_page,
1783 text = _('Most recent'),
1784 select = False
1785 )
1786
1787 # full grid
1788 new_page = cMeasurementsAsTablePnl(self, -1)
1789 new_page.patient = None
1790 self.AddPage (
1791 page = new_page,
1792 text = _('Table'),
1793 select = False
1794 )
1795
1796 # full list
1797 new_page = cMeasurementsAsListPnl(self, -1)
1798 new_page.patient = None
1799 self.AddPage (
1800 page = new_page,
1801 text = _('List'),
1802 select = False
1803 )
1804
1805 #--------------------------------------------------------
1806 # properties
1807 #--------------------------------------------------------
1810
1812 self.__patient = patient
1813 if self.__patient.connected:
1814 pat = self.__patient.patient
1815 else:
1816 pat = None
1817 for page_idx in range(self.GetPageCount()):
1818 page = self.GetPage(page_idx)
1819 page.patient = pat
1820
1821 patient = property(_get_patient, _set_patient)
1822
1823 #================================================================
1825 """A grid class for displaying measurement results.
1826
1827 - operates on a cPatient instance handed to it
1828 - does NOT listen to the currently active patient
1829 - thereby it can display any patient at any time
1830 """
1831 # FIXME: sort-by-battery
1832 # FIXME: filter out empty
1833 # FIXME: filter by tests of a selected date
1834 # FIXME: dates DESC/ASC by cfg
1835 # FIXME: mouse over column header: display date info
1837
1838 wx.grid.Grid.__init__(self, *args, **kwargs)
1839
1840 self.__patient = None
1841 self.__panel_to_show = None
1842 self.__show_by_panel = False
1843 self.__cell_data = {}
1844 self.__row_label_data = []
1845 self.__col_label_data = []
1846
1847 self.__prev_row = None
1848 self.__prev_col = None
1849 self.__prev_label_row = None
1850 self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::'))
1851
1852 self.__init_ui()
1853 self.__register_events()
1854
1855 #------------------------------------------------------------
1856 # external API
1857 #------------------------------------------------------------
1859 if not self.IsSelection():
1860 gmDispatcher.send(signal = 'statustext', msg = _('No results selected for deletion.'))
1861 return True
1862
1863 selected_cells = self.get_selected_cells()
1864 if len(selected_cells) > 20:
1865 results = None
1866 msg = _(
1867 'There are %s results marked for deletion.\n'
1868 '\n'
1869 'Are you sure you want to delete these results ?'
1870 ) % len(selected_cells)
1871 else:
1872 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1873 txt = '\n'.join([ '%s %s (%s): %s %s%s' % (
1874 r['clin_when'].strftime('%x %H:%M'),
1875 r['unified_abbrev'],
1876 r['unified_name'],
1877 r['unified_val'],
1878 r['val_unit'],
1879 gmTools.coalesce(r['abnormality_indicator'], '', ' (%s)')
1880 ) for r in results
1881 ])
1882 msg = _(
1883 'The following results are marked for deletion:\n'
1884 '\n'
1885 '%s\n'
1886 '\n'
1887 'Are you sure you want to delete these results ?'
1888 ) % txt
1889
1890 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
1891 self,
1892 -1,
1893 caption = _('Deleting test results'),
1894 question = msg,
1895 button_defs = [
1896 {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False},
1897 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True}
1898 ]
1899 )
1900 decision = dlg.ShowModal()
1901
1902 if decision == wx.ID_YES:
1903 if results is None:
1904 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1905 for result in results:
1906 gmPathLab.delete_test_result(result)
1907
1908 #------------------------------------------------------------
1910 if not self.IsSelection():
1911 gmDispatcher.send(signal = 'statustext', msg = _('Cannot sign results. No results selected.'))
1912 return True
1913
1914 selected_cells = self.get_selected_cells()
1915 tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
1916
1917 return review_tests(parent = self, tests = tests)
1918
1919 #------------------------------------------------------------
1921
1922 if not self.IsSelection():
1923 gmDispatcher.send(signal = 'statustext', msg = _('Cannot plot results. No results selected.'))
1924 return True
1925
1926 tests = self.__cells_to_data (
1927 cells = self.get_selected_cells(),
1928 exclude_multi_cells = False,
1929 auto_include_multi_cells = True
1930 )
1931
1932 plot_measurements(parent = self, tests = tests)
1933
1934 #------------------------------------------------------------
1936 """Assemble list of all selected cells."""
1937
1938 all_selected_cells = []
1939 # individually selected cells (ctrl-click)
1940 all_selected_cells += [ cell_coords.Get() for cell_coords in self.GetSelectedCells() ]
1941 # add cells from fully selected rows
1942 fully_selected_rows = self.GetSelectedRows()
1943 all_selected_cells += list (
1944 (row, col)
1945 for row in fully_selected_rows
1946 for col in range(self.GetNumberCols())
1947 )
1948 # add cells from fully selected columns
1949 fully_selected_cols = self.GetSelectedCols()
1950 all_selected_cells += list (
1951 (row, col)
1952 for row in range(self.GetNumberRows())
1953 for col in fully_selected_cols
1954 )
1955 # add cells from selection blocks
1956 selected_blocks = zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight())
1957 for top_left_corner, bottom_right_corner in selected_blocks:
1958 all_selected_cells += [
1959 (row, col)
1960 for row in range(top_left_corner[0], bottom_right_corner[0] + 1)
1961 for col in range(top_left_corner[1], bottom_right_corner[1] + 1)
1962 ]
1963 return set(all_selected_cells)
1964
1965 #------------------------------------------------------------
1966 - def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
1967 """Select a range of cells according to criteria.
1968
1969 unsigned_only: include only those which are not signed at all yet
1970 accountable_only: include only those for which the current user is responsible
1971 keep_preselections: broaden (rather than replace) the range of selected cells
1972
1973 Combinations are powerful !
1974 """
1975 wx.BeginBusyCursor()
1976 self.BeginBatch()
1977
1978 if not keep_preselections:
1979 self.ClearSelection()
1980
1981 for col_idx in self.__cell_data.keys():
1982 for row_idx in self.__cell_data[col_idx].keys():
1983 # loop over results in cell and only include
1984 # those multi-value cells that are not ambiguous
1985 do_not_include = False
1986 for result in self.__cell_data[col_idx][row_idx]:
1987 if unsigned_only:
1988 if result['reviewed']:
1989 do_not_include = True
1990 break
1991 if accountables_only:
1992 if not result['you_are_responsible']:
1993 do_not_include = True
1994 break
1995 if do_not_include:
1996 continue
1997
1998 self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True)
1999
2000 self.EndBatch()
2001 wx.EndBusyCursor()
2002
2003 #------------------------------------------------------------
2005 self.empty_grid()
2006 if self.__patient is None:
2007 return
2008
2009 if self.__show_by_panel:
2010 if self.__panel_to_show is None:
2011 return
2012 tests = self.__panel_to_show.get_test_types_for_results (
2013 self.__patient.ID,
2014 order_by = 'unified_abbrev',
2015 unique_meta_types = True
2016 )
2017 self.__repopulate_grid (
2018 tests4rows = tests,
2019 test_pks2show = [ tt['pk_test_type'] for tt in self.__panel_to_show['test_types'] ]
2020 )
2021 return
2022
2023 emr = self.__patient.emr
2024 tests = emr.get_test_types_for_results(order_by = 'unified_abbrev', unique_meta_types = True)
2025 self.__repopulate_grid(tests4rows = tests)
2026
2027 #------------------------------------------------------------
2029
2030 if len(tests4rows) == 0:
2031 return
2032
2033 emr = self.__patient.emr
2034
2035 self.__row_label_data = tests4rows
2036 row_labels = [ '%s%s' % (
2037 gmTools.bool2subst(test_type['is_fake_meta_type'], '', gmTools.u_sum, ''),
2038 test_type['unified_abbrev']
2039 ) for test_type in self.__row_label_data
2040 ]
2041
2042 self.__col_label_data = [ d['clin_when_day'] for d in emr.get_dates_for_results (
2043 tests = test_pks2show,
2044 reverse_chronological = True
2045 )]
2046 col_labels = [ gmDateTime.pydt_strftime(date, self.__date_format, accuracy = gmDateTime.acc_days) for date in self.__col_label_data ]
2047
2048 results = emr.get_test_results_by_date (
2049 tests = test_pks2show,
2050 reverse_chronological = True
2051 )
2052
2053 self.BeginBatch()
2054
2055 # rows
2056 self.AppendRows(numRows = len(row_labels))
2057 for row_idx in range(len(row_labels)):
2058 self.SetRowLabelValue(row_idx, row_labels[row_idx])
2059
2060 # columns
2061 self.AppendCols(numCols = len(col_labels))
2062 for col_idx in range(len(col_labels)):
2063 self.SetColLabelValue(col_idx, col_labels[col_idx])
2064
2065 # cell values (list of test results)
2066 for result in results:
2067 row_idx = row_labels.index('%s%s' % (
2068 gmTools.bool2subst(result['is_fake_meta_type'], '', gmTools.u_sum, ''),
2069 result['unified_abbrev']
2070 ))
2071 col_idx = col_labels.index(gmDateTime.pydt_strftime(result['clin_when'], self.__date_format, accuracy = gmDateTime.acc_days))
2072
2073 try:
2074 self.__cell_data[col_idx]
2075 except KeyError:
2076 self.__cell_data[col_idx] = {}
2077
2078 # the tooltip always shows the youngest sub result details
2079 if row_idx in self.__cell_data[col_idx]:
2080 self.__cell_data[col_idx][row_idx].append(result)
2081 self.__cell_data[col_idx][row_idx].sort(key = lambda x: x['clin_when'], reverse = True)
2082 else:
2083 self.__cell_data[col_idx][row_idx] = [result]
2084
2085 # rebuild cell display string
2086 vals2display = []
2087 cell_has_out_of_bounds_value = False
2088 for sub_result in self.__cell_data[col_idx][row_idx]:
2089
2090 if sub_result.is_considered_abnormal:
2091 cell_has_out_of_bounds_value = True
2092
2093 abnormality_indicator = sub_result.formatted_abnormality_indicator
2094 if abnormality_indicator is None:
2095 abnormality_indicator = ''
2096 if abnormality_indicator != '':
2097 abnormality_indicator = ' (%s)' % abnormality_indicator[:3]
2098
2099 missing_review = False
2100 # warn on missing review if
2101 # a) no review at all exists or
2102 if not sub_result['reviewed']:
2103 missing_review = True
2104 # b) there is a review but
2105 else:
2106 # current user is reviewer and hasn't reviewed
2107 if sub_result['you_are_responsible'] and not sub_result['review_by_you']:
2108 missing_review = True
2109
2110 needs_superscript = False
2111
2112 # can we display the full sub_result length ?
2113 if sub_result.is_long_text:
2114 lines = gmTools.strip_empty_lines (
2115 text = sub_result['unified_val'],
2116 eol = '\n',
2117 return_list = True
2118 )
2119 needs_superscript = True
2120 tmp = lines[0][:7]
2121 else:
2122 val = gmTools.strip_empty_lines (
2123 text = sub_result['unified_val'],
2124 eol = '\n',
2125 return_list = False
2126 ).replace('\n', '//')
2127 if len(val) > 8:
2128 needs_superscript = True
2129 tmp = val[:7]
2130 else:
2131 tmp = '%.8s' % val[:8]
2132
2133 # abnormal ?
2134 tmp = '%s%.6s' % (tmp, abnormality_indicator)
2135
2136 # is there a comment ?
2137 has_sub_result_comment = gmTools.coalesce (
2138 gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']),
2139 ''
2140 ).strip() != ''
2141 if has_sub_result_comment:
2142 needs_superscript = True
2143
2144 if needs_superscript:
2145 tmp = '%s%s' % (tmp, gmTools.u_superscript_one)
2146
2147 # lacking a review ?
2148 if missing_review:
2149 tmp = '%s %s' % (tmp, gmTools.u_writing_hand)
2150 else:
2151 if sub_result['is_clinically_relevant']:
2152 tmp += ' !'
2153
2154 # part of a multi-result cell ?
2155 if len(self.__cell_data[col_idx][row_idx]) > 1:
2156 tmp = '%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp)
2157
2158 vals2display.append(tmp)
2159
2160 self.SetCellValue(row_idx, col_idx, '\n'.join(vals2display))
2161 self.SetCellAlignment(row_idx, col_idx, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
2162 # We used to color text in cells holding abnormals
2163 # in firebrick red but that would color ALL text (including
2164 # normals) and not only the abnormals within that
2165 # cell. Shading, however, only says that *something*
2166 # inside that cell is worthy of attention.
2167 #if sub_result_relevant:
2168 # font = self.GetCellFont(row_idx, col_idx)
2169 # self.SetCellTextColour(row_idx, col_idx, 'firebrick')
2170 # font.SetWeight(wx.FONTWEIGHT_BOLD)
2171 # self.SetCellFont(row_idx, col_idx, font)
2172 if cell_has_out_of_bounds_value:
2173 #self.SetCellBackgroundColour(row_idx, col_idx, 'cornflower blue')
2174 self.SetCellBackgroundColour(row_idx, col_idx, 'PALE TURQUOISE')
2175
2176 self.EndBatch()
2177
2178 self.AutoSize()
2179 self.AdjustScrollbars()
2180 self.ForceRefresh()
2181
2182 #self.Fit()
2183
2184 return
2185
2186 #------------------------------------------------------------
2188 self.BeginBatch()
2189 self.ClearGrid()
2190 # Windows cannot do nothing, it rather decides to assert()
2191 # on thinking it is supposed to do nothing
2192 if self.GetNumberRows() > 0:
2193 self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
2194 if self.GetNumberCols() > 0:
2195 self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
2196 self.EndBatch()
2197 self.__cell_data = {}
2198 self.__row_label_data = []
2199 self.__col_label_data = []
2200
2201 #------------------------------------------------------------
2203 # include details about test types included ?
2204
2205 # sometimes, for some reason, there is no row and
2206 # wxPython still tries to find a tooltip for it
2207 try:
2208 tt = self.__row_label_data[row]
2209 except IndexError:
2210 return ' '
2211
2212 if tt['is_fake_meta_type']:
2213 return tt.format(patient = self.__patient.ID)
2214
2215 meta_tt = tt.meta_test_type
2216 txt = meta_tt.format(with_tests = True, patient = self.__patient.ID)
2217
2218 return txt
2219
2220 #------------------------------------------------------------
2222 try:
2223 cell_results = self.__cell_data[col][row]
2224 except KeyError:
2225 # FIXME: maybe display the most recent or when the most recent was ?
2226 cell_results = None
2227
2228 if cell_results is None:
2229 return ' '
2230
2231 is_multi_cell = False
2232 if len(cell_results) > 1:
2233 is_multi_cell = True
2234 result = cell_results[0]
2235
2236 tt = ''
2237 # header
2238 if is_multi_cell:
2239 tt += _('Details of most recent (topmost) result ! \n')
2240 if result.is_long_text:
2241 tt += gmTools.strip_empty_lines(text = result['val_alpha'], eol = '\n', return_list = False)
2242 return tt
2243
2244 tt += result.format(with_review = True, with_evaluation = True, with_ranges = True)
2245 return tt
2246
2247 #------------------------------------------------------------
2248 # internal helpers
2249 #------------------------------------------------------------
2251 #self.SetMinSize(wx.DefaultSize)
2252 self.SetMinSize((10, 10))
2253
2254 self.CreateGrid(0, 1)
2255 self.EnableEditing(0)
2256 self.EnableDragGridSize(1)
2257
2258 # column labels
2259 # setting this screws up the labels: they are cut off and displaced
2260 #self.SetColLabelAlignment(wx.ALIGN_CENTER, wx.ALIGN_BOTTOM)
2261
2262 # row labels
2263 self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE) # starting with 2.8.8
2264 #self.SetRowLabelSize(150)
2265 self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE)
2266 font = self.GetLabelFont()
2267 font.SetWeight(wx.FONTWEIGHT_LIGHT)
2268 self.SetLabelFont(font)
2269
2270 # add link to left upper corner
2271 dbcfg = gmCfg.cCfgSQL()
2272 url = dbcfg.get2 (
2273 option = 'external.urls.measurements_encyclopedia',
2274 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
2275 bias = 'user',
2276 default = gmPathLab.URL_test_result_information
2277 )
2278
2279 self.__WIN_corner = self.GetGridCornerLabelWindow() # a wx.Window instance
2280
2281 LNK_lab = wxh.HyperlinkCtrl (
2282 self.__WIN_corner,
2283 -1,
2284 label = _('Tests'),
2285 style = wxh.HL_DEFAULT_STYLE # wx.TE_READONLY|wx.TE_CENTRE| wx.NO_BORDER |
2286 )
2287 LNK_lab.SetURL(url)
2288 LNK_lab.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND))
2289 LNK_lab.SetToolTip(_(
2290 'Navigate to an encyclopedia of measurements\n'
2291 'and test methods on the web.\n'
2292 '\n'
2293 ' <%s>'
2294 ) % url)
2295
2296 SZR_inner = wx.BoxSizer(wx.HORIZONTAL)
2297 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer
2298 SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0) #wx.ALIGN_CENTER wx.EXPAND
2299 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer
2300
2301 SZR_corner = wx.BoxSizer(wx.VERTICAL)
2302 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer
2303 SZR_corner.Add(SZR_inner, 0, wx.EXPAND) # inner sizer with centered hyperlink
2304 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer
2305
2306 self.__WIN_corner.SetSizer(SZR_corner)
2307 SZR_corner.Fit(self.__WIN_corner)
2308
2309 #------------------------------------------------------------
2312
2313 #------------------------------------------------------------
2314 - def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
2315 """List of <cells> must be in row / col order."""
2316 data = []
2317 for row, col in cells:
2318 try:
2319 # cell data is stored col / row
2320 data_list = self.__cell_data[col][row]
2321 except KeyError:
2322 continue
2323
2324 if len(data_list) == 1:
2325 data.append(data_list[0])
2326 continue
2327
2328 if exclude_multi_cells:
2329 gmDispatcher.send(signal = 'statustext', msg = _('Excluding multi-result field from further processing.'))
2330 continue
2331
2332 if auto_include_multi_cells:
2333 data.extend(data_list)
2334 continue
2335
2336 data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list)
2337 if data_to_include is None:
2338 continue
2339 data.extend(data_to_include)
2340
2341 return data
2342
2343 #------------------------------------------------------------
2345 data = gmListWidgets.get_choices_from_list (
2346 parent = self,
2347 msg = _(
2348 'Your selection includes a field with multiple results.\n'
2349 '\n'
2350 'Please select the individual results you want to work on:'
2351 ),
2352 caption = _('Selecting test results'),
2353 choices = [ [d['clin_when'], '%s: %s' % (d['abbrev_tt'], d['name_tt']), d['unified_val']] for d in cell_data ],
2354 columns = [ _('Date / Time'), _('Test'), _('Result') ],
2355 data = cell_data,
2356 single_selection = single_selection
2357 )
2358 return data
2359
2360 #------------------------------------------------------------
2361 # event handling
2362 #------------------------------------------------------------
2364 # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow
2365 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
2366 self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels)
2367 #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels)
2368
2369 # sizing left upper corner window
2370 self.Bind(wx.EVT_SIZE, self.__resize_corner_window)
2371
2372 # editing cells
2373 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
2374
2375 #------------------------------------------------------------
2377 col = evt.GetCol()
2378 row = evt.GetRow()
2379
2380 try:
2381 self.__cell_data[col][row]
2382 except KeyError: # empty cell
2383 presets = {}
2384 col_date = self.__col_label_data[col]
2385 presets['clin_when'] = {'data': col_date}
2386 test_type = self.__row_label_data[row]
2387 if test_type['pk_meta_test_type'] is not None:
2388 temporally_closest_result_of_row_type = test_type.meta_test_type.get_temporally_closest_result(col_date, self.__patient.ID)
2389 if temporally_closest_result_of_row_type is not None:
2390 # pre-set test type field to test type of
2391 # "temporally most adjacent" existing result :-)
2392 presets['pk_test_type'] = {'data': temporally_closest_result_of_row_type['pk_test_type']}
2393 # one might also, instead of considering only the "temporally most adjacent"
2394 # one, look at the most adjacent one coming from the same *lab* as other
2395 # results on the desired data ....
2396 same_day_results = gmPathLab.get_results_for_day (
2397 timestamp = col_date,
2398 patient = self.__patient.ID,
2399 order_by = None
2400 )
2401 if len(same_day_results) > 0:
2402 # pre-set episode field to episode of
2403 # existing results on the day in question
2404 presets['pk_episode'] = {'data': same_day_results[0]['pk_episode']}
2405 # maybe ['comment'] as in "medical context" ? - not thought through yet
2406 # no need to set because because setting pk_test_type will do so:
2407 # presets['val_unit']
2408 # presets['val_normal_min']
2409 # presets['val_normal_max']
2410 # presets['val_normal_range']
2411 # presets['val_target_min']
2412 # presets['val_target_max']
2413 # presets['val_target_range']
2414 edit_measurement (
2415 parent = self,
2416 measurement = None,
2417 single_entry = True,
2418 presets = presets
2419 )
2420 return
2421
2422 if len(self.__cell_data[col][row]) > 1:
2423 data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True)
2424 else:
2425 data = self.__cell_data[col][row][0]
2426
2427 if data is None:
2428 return
2429
2430 edit_measurement(parent = self, measurement = data, single_entry = True)
2431
2432 #------------------------------------------------------------
2433 # def OnMouseMotionRowLabel(self, evt):
2434 # x, y = self.CalcUnscrolledPosition(evt.GetPosition())
2435 # row = self.YToRow(y)
2436 # label = self.table().GetRowHelpValue(row)
2437 # self.GetGridRowLabelWindow().SetToolTip(label or "")
2438 # evt.Skip()
2440
2441 # Use CalcUnscrolledPosition() to get the mouse position within the
2442 # entire grid including what's offscreen
2443 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2444
2445 row = self.YToRow(y)
2446
2447 if self.__prev_label_row == row:
2448 return
2449
2450 self.__prev_label_row == row
2451
2452 evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row))
2453 #------------------------------------------------------------
2454 # def OnMouseMotionColLabel(self, evt):
2455 # x, y = self.CalcUnscrolledPosition(evt.GetPosition())
2456 # col = self.XToCol(x)
2457 # label = self.table().GetColHelpValue(col)
2458 # self.GetGridColLabelWindow().SetToolTip(label or "")
2459 # evt.Skip()
2460 #------------------------------------------------------------
2462 """Calculate where the mouse is and set the tooltip dynamically."""
2463
2464 # Use CalcUnscrolledPosition() to get the mouse position within the
2465 # entire grid including what's offscreen
2466 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
2467
2468 # use this logic to prevent tooltips outside the actual cells
2469 # apply to GetRowSize, too
2470 # tot = 0
2471 # for col in range(self.NumberCols):
2472 # tot += self.GetColSize(col)
2473 # if xpos <= tot:
2474 # self.tool_tip.Tip = 'Tool tip for Column %s' % (
2475 # self.GetColLabelValue(col))
2476 # break
2477 # else: # mouse is in label area beyond the right-most column
2478 # self.tool_tip.Tip = ''
2479
2480 row, col = self.XYToCell(x, y)
2481
2482 if (row == self.__prev_row) and (col == self.__prev_col):
2483 return
2484
2485 self.__prev_row = row
2486 self.__prev_col = col
2487
2488 evt.GetEventObject().SetToolTip(self.get_cell_tooltip(col=col, row=row))
2489
2490 #------------------------------------------------------------
2491 # properties
2492 #------------------------------------------------------------
2495
2499
2500 patient = property(_get_patient, _set_patient)
2501 #------------------------------------------------------------
2505
2506 panel_to_show = property(lambda x:x, _set_panel_to_show)
2507 #------------------------------------------------------------
2511
2512 show_by_panel = property(lambda x:x, _set_show_by_panel)
2513
2514 #================================================================
2515 # integrated measurements plugin
2516 #================================================================
2517 from Gnumed.wxGladeWidgets import wxgMeasurementsPnl
2518
2519 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
2520 """Panel holding a grid with lab data. Used as notebook page."""
2521
2523
2524 wxgMeasurementsPnl.wxgMeasurementsPnl.__init__(self, *args, **kwargs)
2525 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
2526 self.__display_mode = 'grid'
2527 self.__init_ui()
2528 self.__register_interests()
2529 #--------------------------------------------------------
2530 # event handling
2531 #--------------------------------------------------------
2533 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
2534 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
2535 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._schedule_data_reget)
2536 gmDispatcher.connect(signal = 'clin.reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
2537 #--------------------------------------------------------
2540 #--------------------------------------------------------
2544 #--------------------------------------------------------
2547 #--------------------------------------------------------
2551 #--------------------------------------------------------
2555 #--------------------------------------------------------
2558 #--------------------------------------------------------
2564 #--------------------------------------------------------
2567 #--------------------------------------------------------
2587 #--------------------------------------------------------
2589 self._GRID_results_all.sign_current_selection()
2590 #--------------------------------------------------------
2592 self._GRID_results_all.plot_current_selection()
2593 #--------------------------------------------------------
2595 self._GRID_results_all.delete_current_selection()
2596 #--------------------------------------------------------
2599 #--------------------------------------------------------
2601 if panel is None:
2602 self._TCTRL_panel_comment.SetValue('')
2603 self._GRID_results_battery.panel_to_show = None
2604 #self._GRID_results_battery.Hide()
2605 self._PNL_results_battery_grid.Hide()
2606 else:
2607 pnl = self._PRW_panel.GetData(as_instance = True)
2608 self._TCTRL_panel_comment.SetValue(gmTools.coalesce (
2609 pnl['comment'],
2610 ''
2611 ))
2612 self._GRID_results_battery.panel_to_show = pnl
2613 #self._GRID_results_battery.Show()
2614 self._PNL_results_battery_grid.Show()
2615 self._GRID_results_battery.Fit()
2616 self._GRID_results_all.Fit()
2617 self.Layout()
2618 #--------------------------------------------------------
2621 #--------------------------------------------------------
2623 self._TCTRL_panel_comment.SetValue('')
2624 if self._PRW_panel.GetValue().strip() == '':
2625 self._GRID_results_battery.panel_to_show = None
2626 #self._GRID_results_battery.Hide()
2627 self._PNL_results_battery_grid.Hide()
2628 self.Layout()
2629 #--------------------------------------------------------
2630 # internal API
2631 #--------------------------------------------------------
2633 self.SetMinSize((10, 10))
2634
2635 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:'))
2636
2637 item = self.__action_button_popup.Append(-1, _('Review and &sign'))
2638 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item)
2639
2640 item = self.__action_button_popup.Append(-1, _('Plot'))
2641 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item)
2642
2643 item = self.__action_button_popup.Append(-1, _('Export to &file'))
2644 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item)
2645 self.__action_button_popup.Enable(id = menu_id, enable = False)
2646
2647 item = self.__action_button_popup.Append(-1, _('Export to &clipboard'))
2648 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item)
2649 self.__action_button_popup.Enable(id = menu_id, enable = False)
2650
2651 item = self.__action_button_popup.Append(-1, _('&Delete'))
2652 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item)
2653
2654 # FIXME: create inbox message to staff to phone patient to come in
2655 # FIXME: generate and let edit a SOAP narrative and include the values
2656
2657 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected)
2658 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
2659
2660 self._GRID_results_battery.show_by_panel = True
2661 self._GRID_results_battery.panel_to_show = None
2662 #self._GRID_results_battery.Hide()
2663 self._PNL_results_battery_grid.Hide()
2664 self._BTN_display_mode.SetLabel(_('All: by &Day'))
2665 #self._GRID_results_all.Show()
2666 self._PNL_results_all_grid.Show()
2667 self._PNL_results_all_listed.Hide()
2668 self.Layout()
2669
2670 self._PRW_panel.SetFocus()
2671 #--------------------------------------------------------
2672 # reget mixin API
2673 #--------------------------------------------------------
2675 pat = gmPerson.gmCurrentPatient()
2676 if pat.connected:
2677 self._GRID_results_battery.patient = pat
2678 if self.__display_mode == 'grid':
2679 self._GRID_results_all.patient = pat
2680 self._PNL_results_all_listed.patient = None
2681 else:
2682 self._GRID_results_all.patient = None
2683 self._PNL_results_all_listed.patient = pat
2684 else:
2685 self._GRID_results_battery.patient = None
2686 self._GRID_results_all.patient = None
2687 self._PNL_results_all_listed.patient = None
2688 return True
2689
2690 #================================================================
2691 # editing widgets
2692 #================================================================
2694
2695 if tests is None:
2696 return True
2697
2698 if len(tests) == 0:
2699 return True
2700
2701 if parent is None:
2702 parent = wx.GetApp().GetTopWindow()
2703
2704 if len(tests) > 10:
2705 test_count = len(tests)
2706 tests2show = None
2707 else:
2708 test_count = None
2709 tests2show = tests
2710 if len(tests) == 0:
2711 return True
2712
2713 dlg = cMeasurementsReviewDlg(parent, -1, tests = tests, test_count = test_count)
2714 decision = dlg.ShowModal()
2715 if decision != wx.ID_APPLY:
2716 return True
2717
2718 wx.BeginBusyCursor()
2719 if dlg._RBTN_confirm_abnormal.GetValue():
2720 abnormal = None
2721 elif dlg._RBTN_results_normal.GetValue():
2722 abnormal = False
2723 else:
2724 abnormal = True
2725
2726 if dlg._RBTN_confirm_relevance.GetValue():
2727 relevant = None
2728 elif dlg._RBTN_results_not_relevant.GetValue():
2729 relevant = False
2730 else:
2731 relevant = True
2732
2733 comment = None
2734 if len(tests) == 1:
2735 comment = dlg._TCTRL_comment.GetValue()
2736
2737 make_responsible = dlg._CHBOX_responsible.IsChecked()
2738 dlg.DestroyLater()
2739
2740 for test in tests:
2741 test.set_review (
2742 technically_abnormal = abnormal,
2743 clinically_relevant = relevant,
2744 comment = comment,
2745 make_me_responsible = make_responsible
2746 )
2747 wx.EndBusyCursor()
2748
2749 return True
2750
2751 #----------------------------------------------------------------
2752 from Gnumed.wxGladeWidgets import wxgMeasurementsReviewDlg
2753
2755
2757
2758 try:
2759 tests = kwargs['tests']
2760 del kwargs['tests']
2761 test_count = len(tests)
2762 try: del kwargs['test_count']
2763 except KeyError: pass
2764 except KeyError:
2765 tests = None
2766 test_count = kwargs['test_count']
2767 del kwargs['test_count']
2768
2769 wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs)
2770
2771 if tests is None:
2772 msg = _('%s results selected. Too many to list individually.') % test_count
2773 else:
2774 msg = '\n'.join (
2775 [ '%s: %s %s (%s)' % (
2776 t['unified_abbrev'],
2777 t['unified_val'],
2778 t['val_unit'],
2779 gmDateTime.pydt_strftime(t['clin_when'], '%Y %b %d')
2780 ) for t in tests
2781 ]
2782 )
2783
2784 self._LBL_tests.SetLabel(msg)
2785
2786 if test_count == 1:
2787 self._TCTRL_comment.Enable(True)
2788 self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], ''))
2789 if tests[0]['you_are_responsible']:
2790 self._CHBOX_responsible.Enable(False)
2791
2792 self.Fit()
2793 #--------------------------------------------------------
2794 # event handling
2795 #--------------------------------------------------------
2801
2802 #================================================================
2803 from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl
2804
2805 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
2806 """This edit area saves *new* measurements into the active patient only."""
2807
2809
2810 try:
2811 self.__default_date = kwargs['date']
2812 del kwargs['date']
2813 except KeyError:
2814 self.__default_date = None
2815
2816 wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl.__init__(self, *args, **kwargs)
2817 gmEditArea.cGenericEditAreaMixin.__init__(self)
2818
2819 self.__register_interests()
2820
2821 self.successful_save_msg = _('Successfully saved measurement.')
2822
2823 self._DPRW_evaluated.display_accuracy = gmDateTime.acc_minutes
2824
2825 #--------------------------------------------------------
2826 # generic edit area mixin API
2827 #----------------------------------------------------------------
2829 try:
2830 self._PRW_test.SetData(data = fields['pk_test_type']['data'])
2831 except KeyError:
2832 pass
2833 try:
2834 self._DPRW_evaluated.SetData(data = fields['clin_when']['data'])
2835 except KeyError:
2836 pass
2837 try:
2838 self._PRW_problem.SetData(data = fields['pk_episode']['data'])
2839 except KeyError:
2840 pass
2841 try:
2842 self._PRW_units.SetText(fields['val_unit']['data'], fields['val_unit']['data'], True)
2843 except KeyError:
2844 pass
2845 try:
2846 self._TCTRL_normal_min.SetValue(fields['val_normal_min']['data'])
2847 except KeyError:
2848 pass
2849 try:
2850 self._TCTRL_normal_max.SetValue(fields['val_normal_max']['data'])
2851 except KeyError:
2852 pass
2853 try:
2854 self._TCTRL_normal_range.SetValue(fields['val_normal_range']['data'])
2855 except KeyError:
2856 pass
2857 try:
2858 self._TCTRL_target_min.SetValue(fields['val_target_min']['data'])
2859 except KeyError:
2860 pass
2861 try:
2862 self._TCTRL_target_max.SetValue(fields['val_target_max']['data'])
2863 except KeyError:
2864 pass
2865 try:
2866 self._TCTRL_target_range.SetValue(fields['val_target_range']['data'])
2867 except KeyError:
2868 pass
2869
2870 self._TCTRL_result.SetFocus()
2871
2872 #--------------------------------------------------------
2874 self._PRW_test.SetText('', None, True)
2875 self.__refresh_loinc_info()
2876 self.__refresh_previous_value()
2877 self.__update_units_context()
2878 self._TCTRL_result.SetValue('')
2879 self._PRW_units.SetText('', None, True)
2880 self._PRW_abnormality_indicator.SetText('', None, True)
2881 if self.__default_date is None:
2882 self._DPRW_evaluated.SetData(data = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone))
2883 else:
2884 self._DPRW_evaluated.SetData(data = None)
2885 self._TCTRL_note_test_org.SetValue('')
2886 self._PRW_intended_reviewer.SetData(gmStaff.gmCurrentProvider()['pk_staff'])
2887 self._PRW_problem.SetData()
2888 self._TCTRL_narrative.SetValue('')
2889 self._CHBOX_review.SetValue(False)
2890 self._CHBOX_abnormal.SetValue(False)
2891 self._CHBOX_relevant.SetValue(False)
2892 self._CHBOX_abnormal.Enable(False)
2893 self._CHBOX_relevant.Enable(False)
2894 self._TCTRL_review_comment.SetValue('')
2895 self._TCTRL_normal_min.SetValue('')
2896 self._TCTRL_normal_max.SetValue('')
2897 self._TCTRL_normal_range.SetValue('')
2898 self._TCTRL_target_min.SetValue('')
2899 self._TCTRL_target_max.SetValue('')
2900 self._TCTRL_target_range.SetValue('')
2901 self._TCTRL_norm_ref_group.SetValue('')
2902
2903 self._PRW_test.SetFocus()
2904 #--------------------------------------------------------
2906 self._PRW_test.SetData(data = self.data['pk_test_type'])
2907 self.__refresh_loinc_info()
2908 self.__refresh_previous_value()
2909 self.__update_units_context()
2910 self._TCTRL_result.SetValue(self.data['unified_val'])
2911 self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True)
2912 self._PRW_abnormality_indicator.SetText (
2913 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2914 gmTools.coalesce(self.data['abnormality_indicator'], ''),
2915 True
2916 )
2917 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2918 self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], ''))
2919 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2920 self._PRW_problem.SetData(self.data['pk_episode'])
2921 self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], ''))
2922 self._CHBOX_review.SetValue(False)
2923 self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False))
2924 self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False))
2925 self._CHBOX_abnormal.Enable(False)
2926 self._CHBOX_relevant.Enable(False)
2927 self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], ''))
2928 self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(self.data['val_normal_min'], '')))
2929 self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(self.data['val_normal_max'], '')))
2930 self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], ''))
2931 self._TCTRL_target_min.SetValue(str(gmTools.coalesce(self.data['val_target_min'], '')))
2932 self._TCTRL_target_max.SetValue(str(gmTools.coalesce(self.data['val_target_max'], '')))
2933 self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], ''))
2934 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], ''))
2935
2936 self._TCTRL_result.SetFocus()
2937 #--------------------------------------------------------
2939 self._PRW_test.SetText('', None, True)
2940 self.__refresh_loinc_info()
2941 self.__refresh_previous_value()
2942 self.__update_units_context()
2943 self._TCTRL_result.SetValue('')
2944 self._PRW_units.SetText('', None, True)
2945 self._PRW_abnormality_indicator.SetText('', None, True)
2946 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
2947 self._TCTRL_note_test_org.SetValue('')
2948 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
2949 self._PRW_problem.SetData(self.data['pk_episode'])
2950 self._TCTRL_narrative.SetValue('')
2951 self._CHBOX_review.SetValue(False)
2952 self._CHBOX_abnormal.SetValue(False)
2953 self._CHBOX_relevant.SetValue(False)
2954 self._CHBOX_abnormal.Enable(False)
2955 self._CHBOX_relevant.Enable(False)
2956 self._TCTRL_review_comment.SetValue('')
2957 self._TCTRL_normal_min.SetValue('')
2958 self._TCTRL_normal_max.SetValue('')
2959 self._TCTRL_normal_range.SetValue('')
2960 self._TCTRL_target_min.SetValue('')
2961 self._TCTRL_target_max.SetValue('')
2962 self._TCTRL_target_range.SetValue('')
2963 self._TCTRL_norm_ref_group.SetValue('')
2964
2965 self._PRW_test.SetFocus()
2966 #--------------------------------------------------------
2968
2969 validity = True
2970
2971 if not self._DPRW_evaluated.is_valid_timestamp():
2972 self._DPRW_evaluated.display_as_valid(False)
2973 validity = False
2974 else:
2975 self._DPRW_evaluated.display_as_valid(True)
2976
2977 val = self._TCTRL_result.GetValue().strip()
2978 if val == '':
2979 validity = False
2980 self.display_ctrl_as_valid(self._TCTRL_result, False)
2981 else:
2982 self.display_ctrl_as_valid(self._TCTRL_result, True)
2983 numeric, val = gmTools.input2decimal(val)
2984 if numeric:
2985 if self._PRW_units.GetValue().strip() == '':
2986 self._PRW_units.display_as_valid(False)
2987 validity = False
2988 else:
2989 self._PRW_units.display_as_valid(True)
2990 else:
2991 self._PRW_units.display_as_valid(True)
2992
2993 if self._PRW_problem.GetValue().strip() == '':
2994 self._PRW_problem.display_as_valid(False)
2995 validity = False
2996 else:
2997 self._PRW_problem.display_as_valid(True)
2998
2999 if self._PRW_test.GetValue().strip() == '':
3000 self._PRW_test.display_as_valid(False)
3001 validity = False
3002 else:
3003 self._PRW_test.display_as_valid(True)
3004
3005 if self._PRW_intended_reviewer.GetData() is None:
3006 self._PRW_intended_reviewer.display_as_valid(False)
3007 validity = False
3008 else:
3009 self._PRW_intended_reviewer.display_as_valid(True)
3010
3011 ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max]
3012 for widget in ctrls:
3013 val = widget.GetValue().strip()
3014 if val == '':
3015 continue
3016 try:
3017 decimal.Decimal(val.replace(',', '.', 1))
3018 self.display_ctrl_as_valid(widget, True)
3019 except Exception:
3020 validity = False
3021 self.display_ctrl_as_valid(widget, False)
3022
3023 if validity is False:
3024 self.StatusText = _('Cannot save result. Invalid or missing essential input.')
3025
3026 return validity
3027 #--------------------------------------------------------
3029
3030 emr = gmPerson.gmCurrentPatient().emr
3031
3032 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
3033 if success:
3034 v_num = result
3035 v_al = None
3036 else:
3037 v_al = self._TCTRL_result.GetValue().strip()
3038 v_num = None
3039
3040 pk_type = self._PRW_test.GetData()
3041 if pk_type is None:
3042 abbrev = self._PRW_test.GetValue().strip()
3043 name = self._PRW_test.GetValue().strip()
3044 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3045 lab = manage_measurement_orgs (
3046 parent = self,
3047 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
3048 )
3049 if lab is not None:
3050 lab = lab['pk_test_org']
3051 tt = gmPathLab.create_measurement_type (
3052 lab = lab,
3053 abbrev = abbrev,
3054 name = name,
3055 unit = unit
3056 )
3057 pk_type = tt['pk_test_type']
3058
3059 tr = emr.add_test_result (
3060 episode = self._PRW_problem.GetData(can_create=True, is_open=False),
3061 type = pk_type,
3062 intended_reviewer = self._PRW_intended_reviewer.GetData(),
3063 val_num = v_num,
3064 val_alpha = v_al,
3065 unit = self._PRW_units.GetValue()
3066 )
3067
3068 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
3069
3070 ctrls = [
3071 ('abnormality_indicator', self._PRW_abnormality_indicator),
3072 ('note_test_org', self._TCTRL_note_test_org),
3073 ('comment', self._TCTRL_narrative),
3074 ('val_normal_range', self._TCTRL_normal_range),
3075 ('val_target_range', self._TCTRL_target_range),
3076 ('norm_ref_group', self._TCTRL_norm_ref_group)
3077 ]
3078 for field, widget in ctrls:
3079 tr[field] = widget.GetValue().strip()
3080
3081 ctrls = [
3082 ('val_normal_min', self._TCTRL_normal_min),
3083 ('val_normal_max', self._TCTRL_normal_max),
3084 ('val_target_min', self._TCTRL_target_min),
3085 ('val_target_max', self._TCTRL_target_max)
3086 ]
3087 for field, widget in ctrls:
3088 val = widget.GetValue().strip()
3089 if val == '':
3090 tr[field] = None
3091 else:
3092 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
3093
3094 tr.save_payload()
3095
3096 if self._CHBOX_review.GetValue() is True:
3097 tr.set_review (
3098 technically_abnormal = self._CHBOX_abnormal.GetValue(),
3099 clinically_relevant = self._CHBOX_relevant.GetValue(),
3100 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
3101 make_me_responsible = False
3102 )
3103
3104 self.data = tr
3105
3106 # wx.CallAfter (
3107 # plot_adjacent_measurements,
3108 # test = self.data,
3109 # plot_singular_result = False,
3110 # use_default_template = True
3111 # )
3112
3113 return True
3114 #--------------------------------------------------------
3116
3117 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
3118 if success:
3119 v_num = result
3120 v_al = None
3121 else:
3122 v_num = None
3123 v_al = self._TCTRL_result.GetValue().strip()
3124
3125 pk_type = self._PRW_test.GetData()
3126 if pk_type is None:
3127 abbrev = self._PRW_test.GetValue().strip()
3128 name = self._PRW_test.GetValue().strip()
3129 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3130 lab = manage_measurement_orgs (
3131 parent = self,
3132 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit)
3133 )
3134 if lab is not None:
3135 lab = lab['pk_test_org']
3136 tt = gmPathLab.create_measurement_type (
3137 lab = None,
3138 abbrev = abbrev,
3139 name = name,
3140 unit = unit
3141 )
3142 pk_type = tt['pk_test_type']
3143
3144 tr = self.data
3145
3146 tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False)
3147 tr['pk_test_type'] = pk_type
3148 tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData()
3149 tr['val_num'] = v_num
3150 tr['val_alpha'] = v_al
3151 tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip()
3152 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
3153
3154 ctrls = [
3155 ('abnormality_indicator', self._PRW_abnormality_indicator),
3156 ('note_test_org', self._TCTRL_note_test_org),
3157 ('comment', self._TCTRL_narrative),
3158 ('val_normal_range', self._TCTRL_normal_range),
3159 ('val_target_range', self._TCTRL_target_range),
3160 ('norm_ref_group', self._TCTRL_norm_ref_group)
3161 ]
3162 for field, widget in ctrls:
3163 tr[field] = widget.GetValue().strip()
3164
3165 ctrls = [
3166 ('val_normal_min', self._TCTRL_normal_min),
3167 ('val_normal_max', self._TCTRL_normal_max),
3168 ('val_target_min', self._TCTRL_target_min),
3169 ('val_target_max', self._TCTRL_target_max)
3170 ]
3171 for field, widget in ctrls:
3172 val = widget.GetValue().strip()
3173 if val == '':
3174 tr[field] = None
3175 else:
3176 tr[field] = decimal.Decimal(val.replace(',', '.', 1))
3177
3178 tr.save_payload()
3179
3180 if self._CHBOX_review.GetValue() is True:
3181 tr.set_review (
3182 technically_abnormal = self._CHBOX_abnormal.GetValue(),
3183 clinically_relevant = self._CHBOX_relevant.GetValue(),
3184 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''),
3185 make_me_responsible = False
3186 )
3187
3188 # wx.CallAfter (
3189 # plot_adjacent_measurements,
3190 # test = self.data,
3191 # plot_singular_result = False,
3192 # use_default_template = True
3193 # )
3194
3195 return True
3196 #--------------------------------------------------------
3197 # event handling
3198 #--------------------------------------------------------
3200 self._PRW_test.add_callback_on_lose_focus(self._on_leave_test_prw)
3201 self._PRW_abnormality_indicator.add_callback_on_lose_focus(self._on_leave_indicator_prw)
3202 self._PRW_units.add_callback_on_lose_focus(self._on_leave_unit_prw)
3203 #--------------------------------------------------------
3205 self.__refresh_loinc_info()
3206 self.__refresh_previous_value()
3207 self.__update_units_context()
3208 # only works if we've got a unit set
3209 self.__update_normal_range()
3210 self.__update_clinical_range()
3211 #--------------------------------------------------------
3213 # maybe we've got a unit now ?
3214 self.__update_normal_range()
3215 self.__update_clinical_range()
3216 #--------------------------------------------------------
3218 # if the user hasn't explicitly enabled reviewing
3219 if not self._CHBOX_review.GetValue():
3220 self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != '')
3221 #--------------------------------------------------------
3223 self._CHBOX_abnormal.Enable(self._CHBOX_review.GetValue())
3224 self._CHBOX_relevant.Enable(self._CHBOX_review.GetValue())
3225 self._TCTRL_review_comment.Enable(self._CHBOX_review.GetValue())
3226 #--------------------------------------------------------
3242 #--------------------------------------------------------
3246 #--------------------------------------------------------
3247 # internal helpers
3248 #--------------------------------------------------------
3250
3251 if self._PRW_test.GetData() is None:
3252 self._PRW_units.unset_context(context = 'pk_type')
3253 self._PRW_units.unset_context(context = 'loinc')
3254 if self._PRW_test.GetValue().strip() == '':
3255 self._PRW_units.unset_context(context = 'test_name')
3256 else:
3257 self._PRW_units.set_context(context = 'test_name', val = self._PRW_test.GetValue().strip())
3258 return
3259
3260 tt = self._PRW_test.GetData(as_instance = True)
3261
3262 self._PRW_units.set_context(context = 'pk_type', val = tt['pk_test_type'])
3263 self._PRW_units.set_context(context = 'test_name', val = tt['name'])
3264
3265 if tt['loinc'] is not None:
3266 self._PRW_units.set_context(context = 'loinc', val = tt['loinc'])
3267
3268 # closest unit
3269 if self._PRW_units.GetValue().strip() == '':
3270 clin_when = self._DPRW_evaluated.GetData()
3271 if clin_when is None:
3272 unit = tt.temporally_closest_unit
3273 else:
3274 clin_when = clin_when.get_pydt()
3275 unit = tt.get_temporally_closest_unit(timestamp = clin_when)
3276 if unit is None:
3277 self._PRW_units.SetText('', unit, True)
3278 else:
3279 self._PRW_units.SetText(unit, unit, True)
3280
3281 #--------------------------------------------------------
3283 unit = self._PRW_units.GetValue().strip()
3284 if unit == '':
3285 return
3286 if self._PRW_test.GetData() is None:
3287 return
3288 for ctrl in [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_normal_range, self._TCTRL_norm_ref_group]:
3289 if ctrl.GetValue().strip() != '':
3290 return
3291 tt = self._PRW_test.GetData(as_instance = True)
3292 test_w_range = tt.get_temporally_closest_normal_range (
3293 unit,
3294 timestamp = self._DPRW_evaluated.GetData().get_pydt()
3295 )
3296 if test_w_range is None:
3297 return
3298 self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(test_w_range['val_normal_min'], '')))
3299 self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(test_w_range['val_normal_max'], '')))
3300 self._TCTRL_normal_range.SetValue(gmTools.coalesce(test_w_range['val_normal_range'], ''))
3301 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(test_w_range['norm_ref_group'], ''))
3302
3303 #--------------------------------------------------------
3305 unit = self._PRW_units.GetValue().strip()
3306 if unit == '':
3307 return
3308 if self._PRW_test.GetData() is None:
3309 return
3310 for ctrl in [self._TCTRL_target_min, self._TCTRL_target_max, self._TCTRL_target_range]:
3311 if ctrl.GetValue().strip() != '':
3312 return
3313 tt = self._PRW_test.GetData(as_instance = True)
3314 test_w_range = tt.get_temporally_closest_target_range (
3315 unit,
3316 gmPerson.gmCurrentPatient().ID,
3317 timestamp = self._DPRW_evaluated.GetData().get_pydt()
3318 )
3319 if test_w_range is None:
3320 return
3321 self._TCTRL_target_min.SetValue(str(gmTools.coalesce(test_w_range['val_target_min'], '')))
3322 self._TCTRL_target_max.SetValue(str(gmTools.coalesce(test_w_range['val_target_max'], '')))
3323 self._TCTRL_target_range.SetValue(gmTools.coalesce(test_w_range['val_target_range'], ''))
3324
3325 #--------------------------------------------------------
3327
3328 self._TCTRL_loinc.SetValue('')
3329
3330 if self._PRW_test.GetData() is None:
3331 return
3332
3333 tt = self._PRW_test.GetData(as_instance = True)
3334
3335 if tt['loinc'] is None:
3336 return
3337
3338 info = gmLOINC.loinc2term(loinc = tt['loinc'])
3339 if len(info) == 0:
3340 self._TCTRL_loinc.SetValue('')
3341 return
3342
3343 self._TCTRL_loinc.SetValue('%s: %s' % (tt['loinc'], info[0]))
3344
3345 #--------------------------------------------------------
3347 self._TCTRL_previous_value.SetValue('')
3348 # it doesn't make much sense to show the most
3349 # recent value when editing an existing one
3350 if self.data is not None:
3351 return
3352
3353 if self._PRW_test.GetData() is None:
3354 return
3355
3356 tt = self._PRW_test.GetData(as_instance = True)
3357 most_recent_results = tt.get_most_recent_results (
3358 max_no_of_results = 1,
3359 patient = gmPerson.gmCurrentPatient().ID
3360 )
3361 if len(most_recent_results) == 0:
3362 return
3363
3364 most_recent = most_recent_results[0]
3365 self._TCTRL_previous_value.SetValue(_('%s ago: %s%s%s - %s%s') % (
3366 gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - most_recent['clin_when']),
3367 most_recent['unified_val'],
3368 most_recent['val_unit'],
3369 gmTools.coalesce(most_recent['abnormality_indicator'], '', ' (%s)'),
3370 most_recent['abbrev_tt'],
3371 gmTools.coalesce(most_recent.formatted_range, '', ' [%s]')
3372 ))
3373 self._TCTRL_previous_value.SetToolTip(most_recent.format (
3374 with_review = True,
3375 with_evaluation = False,
3376 with_ranges = True,
3377 with_episode = True,
3378 with_type_details=True
3379 ))
3380
3381 #================================================================
3382 # measurement type handling
3383 #================================================================
3385
3386 if parent is None:
3387 parent = wx.GetApp().GetTopWindow()
3388
3389 if msg is None:
3390 msg = _('Pick the relevant measurement types.')
3391
3392 if right_column is None:
3393 right_columns = [_('Picked')]
3394 else:
3395 right_columns = [right_column]
3396
3397 picker = gmListWidgets.cItemPickerDlg(parent, -1, msg = msg)
3398 picker.set_columns(columns = [_('Known measurement types')], columns_right = right_columns)
3399 types = gmPathLab.get_measurement_types(order_by = 'unified_abbrev')
3400 picker.set_choices (
3401 choices = [
3402 '%s: %s%s' % (
3403 t['unified_abbrev'],
3404 t['unified_name'],
3405 gmTools.coalesce(t['name_org'], '', ' (%s)')
3406 )
3407 for t in types
3408 ],
3409 data = types
3410 )
3411 if picks is not None:
3412 picker.set_picks (
3413 picks = [
3414 '%s: %s%s' % (
3415 p['unified_abbrev'],
3416 p['unified_name'],
3417 gmTools.coalesce(p['name_org'], '', ' (%s)')
3418 )
3419 for p in picks
3420 ],
3421 data = picks
3422 )
3423 result = picker.ShowModal()
3424
3425 if result == wx.ID_CANCEL:
3426 picker.DestroyLater()
3427 return None
3428
3429 picks = picker.picks
3430 picker.DestroyLater()
3431 return picks
3432
3433 #----------------------------------------------------------------
3435
3436 if parent is None:
3437 parent = wx.GetApp().GetTopWindow()
3438
3439 #------------------------------------------------------------
3440 def edit(test_type=None):
3441 ea = cMeasurementTypeEAPnl(parent, -1, type = test_type)
3442 dlg = gmEditArea.cGenericEditAreaDlg2 (
3443 parent = parent,
3444 id = -1,
3445 edit_area = ea,
3446 single_entry = gmTools.bool2subst((test_type is None), False, True)
3447 )
3448 dlg.SetTitle(gmTools.coalesce(test_type, _('Adding measurement type'), _('Editing measurement type')))
3449
3450 if dlg.ShowModal() == wx.ID_OK:
3451 dlg.DestroyLater()
3452 return True
3453
3454 dlg.DestroyLater()
3455 return False
3456
3457 #------------------------------------------------------------
3458 def delete(measurement_type):
3459 if measurement_type.in_use:
3460 gmDispatcher.send (
3461 signal = 'statustext',
3462 beep = True,
3463 msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev'])
3464 )
3465 return False
3466 gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type'])
3467 return True
3468
3469 #------------------------------------------------------------
3470 def get_tooltip(test_type):
3471 return test_type.format()
3472
3473 #------------------------------------------------------------
3474 def manage_aggregates(test_type):
3475 manage_meta_test_types(parent = parent)
3476 return False
3477
3478 #------------------------------------------------------------
3479 def manage_panels_of_type(test_type):
3480 if test_type['loinc'] is None:
3481 return False
3482 all_panels = gmPathLab.get_test_panels(order_by = 'description')
3483 curr_panels = test_type.test_panels
3484 if curr_panels is None:
3485 curr_panels = []
3486 panel_candidates = [ p for p in all_panels if p['pk_test_panel'] not in [
3487 c_pnl['pk_test_panel'] for c_pnl in curr_panels
3488 ] ]
3489 picker = gmListWidgets.cItemPickerDlg(parent, -1, title = 'Panels with [%s]' % test_type['abbrev'])
3490 picker.set_columns(['Panels available'], ['Panels [%s] is to be on' % test_type['abbrev']])
3491 picker.set_choices (
3492 choices = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in panel_candidates ],
3493 data = panel_candidates
3494 )
3495 picker.set_picks (
3496 picks = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in curr_panels ],
3497 data = curr_panels
3498 )
3499 exit_type = picker.ShowModal()
3500 if exit_type == wx.ID_CANCEL:
3501 return False
3502
3503 # add picked panels which aren't currently in the panel list
3504 panels2add = [ p for p in picker.picks if p['pk_test_panel'] not in [
3505 c_pnl['pk_test_panel'] for c_pnl in curr_panels
3506 ] ]
3507 # remove unpicked panels off the current panel list
3508 panels2remove = [ p for p in curr_panels if p['pk_test_panel'] not in [
3509 picked_pnl['pk_test_panel'] for picked_pnl in picker.picks
3510 ] ]
3511 for new_panel in panels2add:
3512 new_panel.add_loinc(test_type['loinc'])
3513 for stale_panel in panels2remove:
3514 stale_panel.remove_loinc(test_type['loinc'])
3515
3516 return True
3517
3518 #------------------------------------------------------------
3519 def refresh(lctrl):
3520 mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev')
3521 items = [ [
3522 m['abbrev'],
3523 m['name'],
3524 gmTools.coalesce(m['reference_unit'], ''),
3525 gmTools.coalesce(m['loinc'], ''),
3526 gmTools.coalesce(m['comment_type'], ''),
3527 gmTools.coalesce(m['name_org'], '?'),
3528 gmTools.coalesce(m['comment_org'], ''),
3529 m['pk_test_type']
3530 ] for m in mtypes ]
3531 lctrl.set_string_items(items)
3532 lctrl.set_data(mtypes)
3533
3534 #------------------------------------------------------------
3535 gmListWidgets.get_choices_from_list (
3536 parent = parent,
3537 caption = _('Measurement types.'),
3538 columns = [ _('Abbrev'), _('Name'), _('Unit'), _('LOINC'), _('Comment'), _('Org'), _('Comment'), '#' ],
3539 single_selection = True,
3540 refresh_callback = refresh,
3541 edit_callback = edit,
3542 new_callback = edit,
3543 delete_callback = delete,
3544 list_tooltip_callback = get_tooltip,
3545 left_extra_button = (_('%s &Aggregate') % gmTools.u_sum, _('Manage aggregations (%s) of tests into groups.') % gmTools.u_sum, manage_aggregates),
3546 middle_extra_button = (_('Select panels'), _('Select panels the focussed test type is to belong to.'), manage_panels_of_type)
3547 )
3548
3549 #----------------------------------------------------------------
3551
3553
3554 query = """
3555 SELECT DISTINCT ON (field_label)
3556 pk_test_type AS data,
3557 name
3558 || ' ('
3559 || coalesce (
3560 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
3561 '%(in_house)s'
3562 )
3563 || ')'
3564 AS field_label,
3565 name
3566 || ' ('
3567 || abbrev || ', '
3568 || coalesce(abbrev_meta || ': ' || name_meta || ', ', '')
3569 || coalesce (
3570 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org),
3571 '%(in_house)s'
3572 )
3573 || ')'
3574 AS list_label
3575 FROM
3576 clin.v_test_types c_vtt
3577 WHERE
3578 abbrev_meta %%(fragment_condition)s
3579 OR
3580 name_meta %%(fragment_condition)s
3581 OR
3582 abbrev %%(fragment_condition)s
3583 OR
3584 name %%(fragment_condition)s
3585 ORDER BY field_label
3586 LIMIT 50""" % {'in_house': _('generic / in house lab')}
3587
3588 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3589 mp.setThresholds(1, 2, 4)
3590 mp.word_separators = '[ \t:@]+'
3591 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
3592 self.matcher = mp
3593 self.SetToolTip(_('Select the type of measurement.'))
3594 self.selection_only = False
3595
3596 #------------------------------------------------------------
3598 if self.GetData() is None:
3599 return None
3600
3601 return gmPathLab.cMeasurementType(aPK_obj = self.GetData())
3602
3603 #------------------------------------------------------------
3605 lab = gmPathLab.cTestOrg(aPK_obj = instance['pk_test_org'])
3606 field_label = '%s (%s @ %s)' % (
3607 instance['name'],
3608 lab['unit'],
3609 lab['organization']
3610 )
3611 return self.SetText(value = field_label, data = instance['pk_test_type'])
3612
3613 #------------------------------------------------------------
3616
3617 #---------------------------------------------------------
3620
3621 #----------------------------------------------------------------
3622 from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl
3623
3624 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
3625
3627
3628 try:
3629 data = kwargs['type']
3630 del kwargs['type']
3631 except KeyError:
3632 data = None
3633
3634 wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl.__init__(self, *args, **kwargs)
3635 gmEditArea.cGenericEditAreaMixin.__init__(self)
3636 self.mode = 'new'
3637 self.data = data
3638 if data is not None:
3639 self.mode = 'edit'
3640
3641 self.__init_ui()
3642
3643 #----------------------------------------------------------------
3645
3646 # name phraseweel
3647 query = """
3648 select distinct on (name)
3649 pk,
3650 name
3651 from clin.test_type
3652 where
3653 name %(fragment_condition)s
3654 order by name
3655 limit 50"""
3656 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3657 mp.setThresholds(1, 2, 4)
3658 self._PRW_name.matcher = mp
3659 self._PRW_name.selection_only = False
3660 self._PRW_name.add_callback_on_lose_focus(callback = self._on_name_lost_focus)
3661
3662 # abbreviation
3663 query = """
3664 select distinct on (abbrev)
3665 pk,
3666 abbrev
3667 from clin.test_type
3668 where
3669 abbrev %(fragment_condition)s
3670 order by abbrev
3671 limit 50"""
3672 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
3673 mp.setThresholds(1, 2, 3)
3674 self._PRW_abbrev.matcher = mp
3675 self._PRW_abbrev.selection_only = False
3676
3677 # unit
3678 self._PRW_reference_unit.selection_only = False
3679
3680 # loinc
3681 mp = gmLOINC.cLOINCMatchProvider()
3682 mp.setThresholds(1, 2, 4)
3683 #mp.print_queries = True
3684 #mp.word_separators = '[ \t:@]+'
3685 self._PRW_loinc.matcher = mp
3686 self._PRW_loinc.selection_only = False
3687 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
3688
3689 #----------------------------------------------------------------
3691
3692 test = self._PRW_name.GetValue().strip()
3693
3694 if test == '':
3695 self._PRW_reference_unit.unset_context(context = 'test_name')
3696 return
3697
3698 self._PRW_reference_unit.set_context(context = 'test_name', val = test)
3699
3700 #----------------------------------------------------------------
3702 loinc = self._PRW_loinc.GetData()
3703
3704 if loinc is None:
3705 self._TCTRL_loinc_info.SetValue('')
3706 self._PRW_reference_unit.unset_context(context = 'loinc')
3707 return
3708
3709 self._PRW_reference_unit.set_context(context = 'loinc', val = loinc)
3710
3711 info = gmLOINC.loinc2term(loinc = loinc)
3712 if len(info) == 0:
3713 self._TCTRL_loinc_info.SetValue('')
3714 return
3715
3716 self._TCTRL_loinc_info.SetValue(info[0])
3717
3718 #----------------------------------------------------------------
3719 # generic Edit Area mixin API
3720 #----------------------------------------------------------------
3722
3723 has_errors = False
3724 for field in [self._PRW_name, self._PRW_abbrev, self._PRW_reference_unit]:
3725 if field.GetValue().strip() in ['', None]:
3726 has_errors = True
3727 field.display_as_valid(valid = False)
3728 else:
3729 field.display_as_valid(valid = True)
3730 field.Refresh()
3731
3732 return (not has_errors)
3733
3734 #----------------------------------------------------------------
3736
3737 pk_org = self._PRW_test_org.GetData()
3738 if pk_org is None:
3739 pk_org = gmPathLab.create_test_org (
3740 name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), '')
3741 )['pk_test_org']
3742
3743 tt = gmPathLab.create_measurement_type (
3744 lab = pk_org,
3745 abbrev = self._PRW_abbrev.GetValue().strip(),
3746 name = self._PRW_name.GetValue().strip(),
3747 unit = gmTools.coalesce (
3748 self._PRW_reference_unit.GetData(),
3749 self._PRW_reference_unit.GetValue()
3750 ).strip()
3751 )
3752 if self._PRW_loinc.GetData() is not None:
3753 tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), '')
3754 else:
3755 tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), '')
3756 tt['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), '')
3757 tt['pk_meta_test_type'] = self._PRW_meta_type.GetData()
3758
3759 tt.save()
3760
3761 self.data = tt
3762
3763 return True
3764 #----------------------------------------------------------------
3766
3767 pk_org = self._PRW_test_org.GetData()
3768 if pk_org is None:
3769 pk_org = gmPathLab.create_test_org (
3770 name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), '')
3771 )['pk_test_org']
3772
3773 self.data['pk_test_org'] = pk_org
3774 self.data['abbrev'] = self._PRW_abbrev.GetValue().strip()
3775 self.data['name'] = self._PRW_name.GetValue().strip()
3776 self.data['reference_unit'] = gmTools.coalesce (
3777 self._PRW_reference_unit.GetData(),
3778 self._PRW_reference_unit.GetValue()
3779 ).strip()
3780 old_loinc = self.data['loinc']
3781 if self._PRW_loinc.GetData() is not None:
3782 self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), '')
3783 else:
3784 self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), '')
3785 new_loinc = self.data['loinc']
3786 self.data['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), '')
3787 self.data['pk_meta_test_type'] = self._PRW_meta_type.GetData()
3788 self.data.save()
3789
3790 # was it, AND can it be, on any panel ?
3791 if None not in [old_loinc, new_loinc]:
3792 # would it risk being dropped from any panel ?
3793 if new_loinc != old_loinc:
3794 for panel in gmPathLab.get_test_panels(loincs = [old_loinc]):
3795 pnl_loincs = panel.included_loincs
3796 if new_loinc not in pnl_loincs:
3797 pnl_loincs.append(new_loinc)
3798 panel.included_loincs = pnl_loincs
3799 # do not remove old_loinc as it may sit on another
3800 # test type which we haven't removed it from yet
3801
3802 return True
3803
3804 #----------------------------------------------------------------
3806 self._PRW_name.SetText('', None, True)
3807 self._on_name_lost_focus()
3808 self._PRW_abbrev.SetText('', None, True)
3809 self._PRW_reference_unit.SetText('', None, True)
3810 self._PRW_loinc.SetText('', None, True)
3811 self._on_loinc_lost_focus()
3812 self._TCTRL_comment_type.SetValue('')
3813 self._PRW_test_org.SetText('', None, True)
3814 self._PRW_meta_type.SetText('', None, True)
3815
3816 self._PRW_name.SetFocus()
3817 #----------------------------------------------------------------
3819 self._PRW_name.SetText(self.data['name'], self.data['name'], True)
3820 self._on_name_lost_focus()
3821 self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True)
3822 self._PRW_reference_unit.SetText (
3823 gmTools.coalesce(self.data['reference_unit'], ''),
3824 self.data['reference_unit'],
3825 True
3826 )
3827 self._PRW_loinc.SetText (
3828 gmTools.coalesce(self.data['loinc'], ''),
3829 self.data['loinc'],
3830 True
3831 )
3832 self._on_loinc_lost_focus()
3833 self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], ''))
3834 self._PRW_test_org.SetText (
3835 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']),
3836 self.data['pk_test_org'],
3837 True
3838 )
3839 if self.data['pk_meta_test_type'] is None:
3840 self._PRW_meta_type.SetText('', None, True)
3841 else:
3842 self._PRW_meta_type.SetText('%s: %s' % (self.data['abbrev_meta'], self.data['name_meta']), self.data['pk_meta_test_type'], True)
3843
3844 self._PRW_name.SetFocus()
3845 #----------------------------------------------------------------
3854
3855 #================================================================
3856 _SQL_units_from_test_results = """
3857 -- via clin.v_test_results.pk_type (for types already used in results)
3858 SELECT
3859 val_unit AS data,
3860 val_unit AS field_label,
3861 val_unit || ' (' || name_tt || ')' AS list_label,
3862 1 AS rank
3863 FROM
3864 clin.v_test_results
3865 WHERE
3866 (
3867 val_unit %(fragment_condition)s
3868 OR
3869 reference_unit %(fragment_condition)s
3870 )
3871 %(ctxt_type_pk)s
3872 %(ctxt_test_name)s
3873 """
3874
3875 _SQL_units_from_test_types = """
3876 -- via clin.test_type (for types not yet used in results)
3877 SELECT
3878 reference_unit AS data,
3879 reference_unit AS field_label,
3880 reference_unit || ' (' || name || ')' AS list_label,
3881 2 AS rank
3882 FROM
3883 clin.test_type
3884 WHERE
3885 reference_unit %(fragment_condition)s
3886 %(ctxt_ctt)s
3887 """
3888
3889 _SQL_units_from_loinc_ipcc = """
3890 -- via ref.loinc.ipcc_units
3891 SELECT
3892 ipcc_units AS data,
3893 ipcc_units AS field_label,
3894 ipcc_units || ' (LOINC.ipcc: ' || term || ')' AS list_label,
3895 3 AS rank
3896 FROM
3897 ref.loinc
3898 WHERE
3899 ipcc_units %(fragment_condition)s
3900 %(ctxt_loinc)s
3901 %(ctxt_loinc_term)s
3902 """
3903
3904 _SQL_units_from_loinc_submitted = """
3905 -- via ref.loinc.submitted_units
3906 SELECT
3907 submitted_units AS data,
3908 submitted_units AS field_label,
3909 submitted_units || ' (LOINC.submitted:' || term || ')' AS list_label,
3910 3 AS rank
3911 FROM
3912 ref.loinc
3913 WHERE
3914 submitted_units %(fragment_condition)s
3915 %(ctxt_loinc)s
3916 %(ctxt_loinc_term)s
3917 """
3918
3919 _SQL_units_from_loinc_example = """
3920 -- via ref.loinc.example_units
3921 SELECT
3922 example_units AS data,
3923 example_units AS field_label,
3924 example_units || ' (LOINC.example: ' || term || ')' AS list_label,
3925 3 AS rank
3926 FROM
3927 ref.loinc
3928 WHERE
3929 example_units %(fragment_condition)s
3930 %(ctxt_loinc)s
3931 %(ctxt_loinc_term)s
3932 """
3933
3934 _SQL_units_from_substance_doses = """
3935 -- via ref.v_substance_doses.unit
3936 SELECT
3937 unit AS data,
3938 unit AS field_label,
3939 unit || ' (' || substance || ')' AS list_label,
3940 2 AS rank
3941 FROM
3942 ref.v_substance_doses
3943 WHERE
3944 unit %(fragment_condition)s
3945 %(ctxt_substance)s
3946 """
3947
3948 _SQL_units_from_substance_doses2 = """
3949 -- via ref.v_substance_doses.dose_unit
3950 SELECT
3951 dose_unit AS data,
3952 dose_unit AS field_label,
3953 dose_unit || ' (' || substance || ')' AS list_label,
3954 2 AS rank
3955 FROM
3956 ref.v_substance_doses
3957 WHERE
3958 dose_unit %(fragment_condition)s
3959 %(ctxt_substance)s
3960 """
3961
3962 #----------------------------------------------------------------
3964
3966
3967 query = """
3968 SELECT DISTINCT ON (data)
3969 data,
3970 field_label,
3971 list_label
3972 FROM (
3973
3974 SELECT
3975 data,
3976 field_label,
3977 list_label,
3978 rank
3979 FROM (
3980 (%s) UNION ALL
3981 (%s) UNION ALL
3982 (%s) UNION ALL
3983 (%s) UNION ALL
3984 (%s) UNION ALL
3985 (%s) UNION ALL
3986 (%s)
3987 ) AS all_matching_units
3988 WHERE data IS NOT NULL
3989 ORDER BY rank, list_label
3990
3991 ) AS ranked_matching_units
3992 LIMIT 50""" % (
3993 _SQL_units_from_test_results,
3994 _SQL_units_from_test_types,
3995 _SQL_units_from_loinc_ipcc,
3996 _SQL_units_from_loinc_submitted,
3997 _SQL_units_from_loinc_example,
3998 _SQL_units_from_substance_doses,
3999 _SQL_units_from_substance_doses2
4000 )
4001
4002 ctxt = {
4003 'ctxt_type_pk': {
4004 'where_part': 'AND pk_test_type = %(pk_type)s',
4005 'placeholder': 'pk_type'
4006 },
4007 'ctxt_test_name': {
4008 'where_part': 'AND %(test_name)s IN (name_tt, name_meta, abbrev_meta)',
4009 'placeholder': 'test_name'
4010 },
4011 'ctxt_ctt': {
4012 'where_part': 'AND %(test_name)s IN (name, abbrev)',
4013 'placeholder': 'test_name'
4014 },
4015 'ctxt_loinc': {
4016 'where_part': 'AND code = %(loinc)s',
4017 'placeholder': 'loinc'
4018 },
4019 'ctxt_loinc_term': {
4020 'where_part': 'AND term ~* %(test_name)s',
4021 'placeholder': 'test_name'
4022 },
4023 'ctxt_substance': {
4024 'where_part': 'AND description ~* %(substance)s',
4025 'placeholder': 'substance'
4026 }
4027 }
4028
4029 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = ctxt)
4030 mp.setThresholds(1, 2, 4)
4031 #mp.print_queries = True
4032 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4033 self.matcher = mp
4034 self.SetToolTip(_('Select the desired unit for the amount or measurement.'))
4035 self.selection_only = False
4036 self.phrase_separators = '[;|]+'
4037
4038 #================================================================
4039
4040 #================================================================
4042
4044
4045 query = """
4046 select distinct abnormality_indicator,
4047 abnormality_indicator, abnormality_indicator
4048 from clin.v_test_results
4049 where
4050 abnormality_indicator %(fragment_condition)s
4051 order by abnormality_indicator
4052 limit 25"""
4053
4054 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4055 mp.setThresholds(1, 1, 2)
4056 mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"'
4057 mp.word_separators = '[ \t&:]+'
4058 gmPhraseWheel.cPhraseWheel.__init__ (
4059 self,
4060 *args,
4061 **kwargs
4062 )
4063 self.matcher = mp
4064 self.SetToolTip(_('Select an indicator for the level of abnormality.'))
4065 self.selection_only = False
4066
4067 #================================================================
4068 # measurement org widgets / functions
4069 #----------------------------------------------------------------
4071 ea = cMeasurementOrgEAPnl(parent, -1)
4072 ea.data = org
4073 ea.mode = gmTools.coalesce(org, 'new', 'edit')
4074 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea)
4075 dlg.SetTitle(gmTools.coalesce(org, _('Adding new diagnostic org'), _('Editing diagnostic org')))
4076 if dlg.ShowModal() == wx.ID_OK:
4077 dlg.DestroyLater()
4078 return True
4079 dlg.DestroyLater()
4080 return False
4081 #----------------------------------------------------------------
4083
4084 if parent is None:
4085 parent = wx.GetApp().GetTopWindow()
4086
4087 #------------------------------------------------------------
4088 def edit(org=None):
4089 return edit_measurement_org(parent = parent, org = org)
4090 #------------------------------------------------------------
4091 def refresh(lctrl):
4092 orgs = gmPathLab.get_test_orgs()
4093 lctrl.set_string_items ([
4094 (o['unit'], o['organization'], gmTools.coalesce(o['test_org_contact'], ''), gmTools.coalesce(o['comment'], ''), o['pk_test_org'])
4095 for o in orgs
4096 ])
4097 lctrl.set_data(orgs)
4098 #------------------------------------------------------------
4099 def delete(test_org):
4100 gmPathLab.delete_test_org(test_org = test_org['pk_test_org'])
4101 return True
4102 #------------------------------------------------------------
4103 if msg is None:
4104 msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n')
4105
4106 return gmListWidgets.get_choices_from_list (
4107 parent = parent,
4108 msg = msg,
4109 caption = _('Showing diagnostic orgs.'),
4110 columns = [_('Name'), _('Organization'), _('Contact'), _('Comment'), '#'],
4111 single_selection = True,
4112 refresh_callback = refresh,
4113 edit_callback = edit,
4114 new_callback = edit,
4115 delete_callback = delete
4116 )
4117
4118 #----------------------------------------------------------------
4119 from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl
4120
4121 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
4122
4124
4125 try:
4126 data = kwargs['org']
4127 del kwargs['org']
4128 except KeyError:
4129 data = None
4130
4131 wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl.__init__(self, *args, **kwargs)
4132 gmEditArea.cGenericEditAreaMixin.__init__(self)
4133
4134 self.mode = 'new'
4135 self.data = data
4136 if data is not None:
4137 self.mode = 'edit'
4138
4139 #self.__init_ui()
4140 #----------------------------------------------------------------
4141 # def __init_ui(self):
4142 # # adjust phrasewheels etc
4143 #----------------------------------------------------------------
4144 # generic Edit Area mixin API
4145 #----------------------------------------------------------------
4147 has_errors = False
4148 if self._PRW_org_unit.GetData() is None:
4149 if self._PRW_org_unit.GetValue().strip() == '':
4150 has_errors = True
4151 self._PRW_org_unit.display_as_valid(valid = False)
4152 else:
4153 self._PRW_org_unit.display_as_valid(valid = True)
4154 else:
4155 self._PRW_org_unit.display_as_valid(valid = True)
4156
4157 return (not has_errors)
4158 #----------------------------------------------------------------
4160 data = gmPathLab.create_test_org (
4161 name = self._PRW_org_unit.GetValue().strip(),
4162 comment = self._TCTRL_comment.GetValue().strip(),
4163 pk_org_unit = self._PRW_org_unit.GetData()
4164 )
4165 data['test_org_contact'] = self._TCTRL_contact.GetValue().strip()
4166 data.save()
4167 self.data = data
4168 return True
4169 #----------------------------------------------------------------
4171 # get or create the org unit
4172 name = self._PRW_org_unit.GetValue().strip()
4173 org = gmOrganization.org_exists(organization = name)
4174 if org is None:
4175 org = gmOrganization.create_org (
4176 organization = name,
4177 category = 'Laboratory'
4178 )
4179 org_unit = gmOrganization.create_org_unit (
4180 pk_organization = org['pk_org'],
4181 unit = name
4182 )
4183 # update test_org fields
4184 self.data['pk_org_unit'] = org_unit['pk_org_unit']
4185 self.data['test_org_contact'] = self._TCTRL_contact.GetValue().strip()
4186 self.data['comment'] = self._TCTRL_comment.GetValue().strip()
4187 self.data.save()
4188 return True
4189 #----------------------------------------------------------------
4191 self._PRW_org_unit.SetText(value = '', data = None)
4192 self._TCTRL_contact.SetValue('')
4193 self._TCTRL_comment.SetValue('')
4194 #----------------------------------------------------------------
4196 self._PRW_org_unit.SetText(value = self.data['unit'], data = self.data['pk_org_unit'])
4197 self._TCTRL_contact.SetValue(gmTools.coalesce(self.data['test_org_contact'], ''))
4198 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
4199 #----------------------------------------------------------------
4202 #----------------------------------------------------------------
4205
4206 #----------------------------------------------------------------
4208
4210
4211 query = """
4212 SELECT DISTINCT ON (list_label)
4213 pk_test_org AS data,
4214 unit || ' (' || organization || ')' AS field_label,
4215 unit || ' @ ' || organization AS list_label
4216 FROM clin.v_test_orgs
4217 WHERE
4218 unit %(fragment_condition)s
4219 OR
4220 organization %(fragment_condition)s
4221 ORDER BY list_label
4222 LIMIT 50"""
4223 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4224 mp.setThresholds(1, 2, 4)
4225 #mp.word_separators = '[ \t:@]+'
4226 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4227 self.matcher = mp
4228 self.SetToolTip(_('The name of the path lab/diagnostic organisation.'))
4229 self.selection_only = False
4230 #------------------------------------------------------------
4232 if self.GetData() is not None:
4233 _log.debug('data already set, not creating')
4234 return
4235
4236 if self.GetValue().strip() == '':
4237 _log.debug('cannot create new lab, missing name')
4238 return
4239
4240 lab = gmPathLab.create_test_org(name = self.GetValue().strip())
4241 self.SetText(value = lab['unit'], data = lab['pk_test_org'])
4242 return
4243 #------------------------------------------------------------
4246
4247 #================================================================
4248 # Meta test type widgets
4249 #----------------------------------------------------------------
4251 ea = cMetaTestTypeEAPnl(parent, -1)
4252 ea.data = meta_test_type
4253 ea.mode = gmTools.coalesce(meta_test_type, 'new', 'edit')
4254 dlg = gmEditArea.cGenericEditAreaDlg2 (
4255 parent = parent,
4256 id = -1,
4257 edit_area = ea,
4258 single_entry = gmTools.bool2subst((meta_test_type is None), False, True)
4259 )
4260 dlg.SetTitle(gmTools.coalesce(meta_test_type, _('Adding new meta test type'), _('Editing meta test type')))
4261 if dlg.ShowModal() == wx.ID_OK:
4262 dlg.DestroyLater()
4263 return True
4264 dlg.DestroyLater()
4265 return False
4266
4267 #----------------------------------------------------------------
4269
4270 if parent is None:
4271 parent = wx.GetApp().GetTopWindow()
4272
4273 #------------------------------------------------------------
4274 def edit(meta_test_type=None):
4275 return edit_meta_test_type(parent = parent, meta_test_type = meta_test_type)
4276 #------------------------------------------------------------
4277 def delete(meta_test_type):
4278 gmPathLab.delete_meta_type(meta_type = meta_test_type['pk'])
4279 return True
4280 #----------------------------------------
4281 def get_tooltip(data):
4282 if data is None:
4283 return None
4284 return data.format(with_tests = True)
4285 #------------------------------------------------------------
4286 def refresh(lctrl):
4287 mtts = gmPathLab.get_meta_test_types()
4288 items = [ [
4289 m['abbrev'],
4290 m['name'],
4291 gmTools.coalesce(m['loinc'], ''),
4292 gmTools.coalesce(m['comment'], ''),
4293 m['pk']
4294 ] for m in mtts ]
4295 lctrl.set_string_items(items)
4296 lctrl.set_data(mtts)
4297 #----------------------------------------
4298
4299 msg = _(
4300 '\n'
4301 'These are the meta test types currently defined in GNUmed.\n'
4302 '\n'
4303 'Meta test types allow you to aggregate several actual test types used\n'
4304 'by pathology labs into one logical type.\n'
4305 '\n'
4306 'This is useful for grouping together results of tests which come under\n'
4307 'different names but really are the same thing. This often happens when\n'
4308 'you switch labs or the lab starts using another test method.\n'
4309 )
4310
4311 gmListWidgets.get_choices_from_list (
4312 parent = parent,
4313 msg = msg,
4314 caption = _('Showing meta test types.'),
4315 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), '#'],
4316 single_selection = True,
4317 list_tooltip_callback = get_tooltip,
4318 edit_callback = edit,
4319 new_callback = edit,
4320 delete_callback = delete,
4321 refresh_callback = refresh
4322 )
4323
4324 #----------------------------------------------------------------
4326
4328
4329 query = """
4330 SELECT DISTINCT ON (field_label)
4331 c_mtt.pk
4332 AS data,
4333 c_mtt.abbrev || ': ' || name
4334 AS field_label,
4335 c_mtt.abbrev || ': ' || name
4336 || coalesce (
4337 ' (' || c_mtt.comment || ')',
4338 ''
4339 )
4340 || coalesce (
4341 ', LOINC: ' || c_mtt.loinc,
4342 ''
4343 )
4344 AS list_label
4345 FROM
4346 clin.meta_test_type c_mtt
4347 WHERE
4348 abbrev %(fragment_condition)s
4349 OR
4350 name %(fragment_condition)s
4351 OR
4352 loinc %(fragment_condition)s
4353 ORDER BY field_label
4354 LIMIT 50"""
4355
4356 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4357 mp.setThresholds(1, 2, 4)
4358 mp.word_separators = '[ \t:@]+'
4359 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4360 self.matcher = mp
4361 self.SetToolTip(_('Select the meta test type.'))
4362 self.selection_only = True
4363 #------------------------------------------------------------
4365 if self.GetData() is None:
4366 return None
4367
4368 return gmPathLab.cMetaTestType(aPK_obj = self.GetData())
4369
4370 #----------------------------------------------------------------
4371 from Gnumed.wxGladeWidgets import wxgMetaTestTypeEAPnl
4372
4373 -class cMetaTestTypeEAPnl(wxgMetaTestTypeEAPnl.wxgMetaTestTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
4374
4376
4377 try:
4378 data = kwargs['meta_test_type']
4379 del kwargs['meta_test_type']
4380 except KeyError:
4381 data = None
4382
4383 wxgMetaTestTypeEAPnl.wxgMetaTestTypeEAPnl.__init__(self, *args, **kwargs)
4384 gmEditArea.cGenericEditAreaMixin.__init__(self)
4385
4386 # Code using this mixin should set mode and data
4387 # after instantiating the class:
4388 self.mode = 'new'
4389 self.data = data
4390 if data is not None:
4391 self.mode = 'edit'
4392
4393 self.__init_ui()
4394 #----------------------------------------------------------------
4396 # loinc
4397 mp = gmLOINC.cLOINCMatchProvider()
4398 mp.setThresholds(1, 2, 4)
4399 #mp.print_queries = True
4400 #mp.word_separators = '[ \t:@]+'
4401 self._PRW_loinc.matcher = mp
4402 self._PRW_loinc.selection_only = False
4403 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
4404
4405 #----------------------------------------------------------------
4406 # generic Edit Area mixin API
4407 #----------------------------------------------------------------
4409
4410 validity = True
4411
4412 if self._PRW_abbreviation.GetValue().strip() == '':
4413 validity = False
4414 self._PRW_abbreviation.display_as_valid(False)
4415 self.StatusText = _('Missing abbreviation for meta test type.')
4416 self._PRW_abbreviation.SetFocus()
4417 else:
4418 self._PRW_abbreviation.display_as_valid(True)
4419
4420 if self._PRW_name.GetValue().strip() == '':
4421 validity = False
4422 self._PRW_name.display_as_valid(False)
4423 self.StatusText = _('Missing name for meta test type.')
4424 self._PRW_name.SetFocus()
4425 else:
4426 self._PRW_name.display_as_valid(True)
4427
4428 return validity
4429 #----------------------------------------------------------------
4431
4432 # save the data as a new instance
4433 data = gmPathLab.create_meta_type (
4434 name = self._PRW_name.GetValue().strip(),
4435 abbreviation = self._PRW_abbreviation.GetValue().strip(),
4436 return_existing = False
4437 )
4438 if data is None:
4439 self.StatusText = _('This meta test type already exists.')
4440 return False
4441 data['loinc'] = self._PRW_loinc.GetData()
4442 data['comment'] = self._TCTRL_comment.GetValue().strip()
4443 data.save()
4444 self.data = data
4445 return True
4446 #----------------------------------------------------------------
4448 self.data['name'] = self._PRW_name.GetValue().strip()
4449 self.data['abbrev'] = self._PRW_abbreviation.GetValue().strip()
4450 self.data['loinc'] = self._PRW_loinc.GetData()
4451 self.data['comment'] = self._TCTRL_comment.GetValue().strip()
4452 self.data.save()
4453 return True
4454 #----------------------------------------------------------------
4456 self._PRW_name.SetText('', None)
4457 self._PRW_abbreviation.SetText('', None)
4458 self._PRW_loinc.SetText('', None)
4459 self._TCTRL_loinc_info.SetValue('')
4460 self._TCTRL_comment.SetValue('')
4461 self._LBL_member_detail.SetLabel('')
4462
4463 self._PRW_name.SetFocus()
4464 #----------------------------------------------------------------
4467 #----------------------------------------------------------------
4469 self._PRW_name.SetText(self.data['name'], self.data['pk'])
4470 self._PRW_abbreviation.SetText(self.data['abbrev'], self.data['abbrev'])
4471 self._PRW_loinc.SetText(gmTools.coalesce(self.data['loinc'], ''), self.data['loinc'])
4472 self.__refresh_loinc_info()
4473 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
4474 self.__refresh_members()
4475
4476 self._PRW_name.SetFocus()
4477 #----------------------------------------------------------------
4478 # event handlers
4479 #----------------------------------------------------------------
4482 #----------------------------------------------------------------
4483 # internal helpers
4484 #----------------------------------------------------------------
4486 loinc = self._PRW_loinc.GetData()
4487
4488 if loinc is None:
4489 self._TCTRL_loinc_info.SetValue('')
4490 return
4491
4492 info = gmLOINC.loinc2term(loinc = loinc)
4493 if len(info) == 0:
4494 self._TCTRL_loinc_info.SetValue('')
4495 return
4496
4497 self._TCTRL_loinc_info.SetValue(info[0])
4498 #----------------------------------------------------------------
4500 if self.data is None:
4501 self._LBL_member_detail.SetLabel('')
4502 return
4503
4504 types = self.data.included_test_types
4505 if len(types) == 0:
4506 self._LBL_member_detail.SetLabel('')
4507 return
4508
4509 lines = []
4510 for tt in types:
4511 lines.append('%s (%s%s) [#%s] @ %s' % (
4512 tt['name'],
4513 tt['abbrev'],
4514 gmTools.coalesce(tt['loinc'], '', ', LOINC: %s'),
4515 tt['pk_test_type'],
4516 tt['name_org']
4517 ))
4518 self._LBL_member_detail.SetLabel('\n'.join(lines))
4519
4520 #================================================================
4521 # test panel handling
4522 #================================================================
4524 ea = cTestPanelEAPnl(parent, -1)
4525 ea.data = test_panel
4526 ea.mode = gmTools.coalesce(test_panel, 'new', 'edit')
4527 dlg = gmEditArea.cGenericEditAreaDlg2 (
4528 parent = parent,
4529 id = -1,
4530 edit_area = ea,
4531 single_entry = gmTools.bool2subst((test_panel is None), False, True)
4532 )
4533 dlg.SetTitle(gmTools.coalesce(test_panel, _('Adding new test panel'), _('Editing test panel')))
4534 if dlg.ShowModal() == wx.ID_OK:
4535 dlg.DestroyLater()
4536 return True
4537 dlg.DestroyLater()
4538 return False
4539
4540 #----------------------------------------------------------------
4542
4543 if parent is None:
4544 parent = wx.GetApp().GetTopWindow()
4545
4546 #------------------------------------------------------------
4547 def edit(test_panel=None):
4548 return edit_test_panel(parent = parent, test_panel = test_panel)
4549 #------------------------------------------------------------
4550 def delete(test_panel):
4551 gmPathLab.delete_test_panel(pk = test_panel['pk_test_panel'])
4552 return True
4553 #------------------------------------------------------------
4554 def get_tooltip(test_panel):
4555 return test_panel.format()
4556 #------------------------------------------------------------
4557 def refresh(lctrl):
4558 panels = gmPathLab.get_test_panels(order_by = 'description')
4559 items = [ [
4560 p['description'],
4561 gmTools.coalesce(p['comment'], ''),
4562 p['pk_test_panel']
4563 ] for p in panels ]
4564 lctrl.set_string_items(items)
4565 lctrl.set_data(panels)
4566 #------------------------------------------------------------
4567 gmListWidgets.get_choices_from_list (
4568 parent = parent,
4569 caption = 'GNUmed: ' + _('Test panels list'),
4570 columns = [ _('Name'), _('Comment'), '#' ],
4571 single_selection = True,
4572 refresh_callback = refresh,
4573 edit_callback = edit,
4574 new_callback = edit,
4575 delete_callback = delete,
4576 list_tooltip_callback = get_tooltip
4577 )
4578
4579 #----------------------------------------------------------------
4581
4583 query = """
4584 SELECT
4585 pk_test_panel
4586 AS data,
4587 description
4588 AS field_label,
4589 description
4590 AS list_label
4591 FROM
4592 clin.v_test_panels
4593 WHERE
4594 description %(fragment_condition)s
4595 ORDER BY field_label
4596 LIMIT 30"""
4597 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
4598 mp.setThresholds(1, 2, 4)
4599 #mp.word_separators = '[ \t:@]+'
4600 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
4601 self.matcher = mp
4602 self.SetToolTip(_('Select a test panel.'))
4603 self.selection_only = True
4604 #------------------------------------------------------------
4606 if self.GetData() is None:
4607 return None
4608 return gmPathLab.cTestPanel(aPK_obj = self.GetData())
4609 #------------------------------------------------------------
4611 if self.GetData() is None:
4612 return None
4613 return gmPathLab.cTestPanel(aPK_obj = self.GetData()).format()
4614
4615 #====================================================================
4616 from Gnumed.wxGladeWidgets import wxgTestPanelEAPnl
4617
4619
4621
4622 try:
4623 data = kwargs['panel']
4624 del kwargs['panel']
4625 except KeyError:
4626 data = None
4627
4628 wxgTestPanelEAPnl.wxgTestPanelEAPnl.__init__(self, *args, **kwargs)
4629 gmEditArea.cGenericEditAreaMixin.__init__(self)
4630
4631 self.__loincs = None
4632
4633 self.mode = 'new'
4634 self.data = data
4635 if data is not None:
4636 self.mode = 'edit'
4637
4638 self.__init_ui()
4639
4640 #----------------------------------------------------------------
4642 self._LCTRL_loincs.set_columns([_('LOINC'), _('Term'), _('Units')])
4643 self._LCTRL_loincs.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE])
4644 #self._LCTRL_loincs.set_resize_column(column = 2)
4645 self._LCTRL_loincs.delete_callback = self._remove_loincs_from_list
4646 self.__refresh_loinc_list()
4647
4648 self._PRW_loinc.final_regex = r'.*'
4649 self._PRW_loinc.add_callback_on_selection(callback = self._on_loinc_selected)
4650
4651 #----------------------------------------------------------------
4653 self._LCTRL_loincs.remove_items_safely()
4654 if self.__loincs is None:
4655 if self.data is None:
4656 return
4657 self.__loincs = self.data['loincs']
4658
4659 items = []
4660 for loinc in self.__loincs:
4661 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4662 if loinc_detail is None:
4663 # check for test type with this pseudo loinc
4664 ttypes = gmPathLab.get_measurement_types(loincs = [loinc])
4665 if len(ttypes) == 0:
4666 items.append([loinc, _('LOINC not found'), ''])
4667 else:
4668 for tt in ttypes:
4669 items.append([loinc, _('not a LOINC') + u'; %(name)s @ %(name_org)s [#%(pk_test_type)s]' % tt, ''])
4670 continue
4671 items.append ([
4672 loinc,
4673 loinc_detail['term'],
4674 gmTools.coalesce(loinc_detail['example_units'], '', '%s')
4675 ])
4676
4677 self._LCTRL_loincs.set_string_items(items)
4678 self._LCTRL_loincs.set_column_widths()
4679
4680 #----------------------------------------------------------------
4681 # generic Edit Area mixin API
4682 #----------------------------------------------------------------
4684 validity = True
4685
4686 if self.__loincs is None:
4687 if self.data is not None:
4688 self.__loincs = self.data['loincs']
4689
4690 if self.__loincs is None:
4691 # not fatal despite panel being useless
4692 self.StatusText = _('No LOINC codes selected.')
4693 self._PRW_loinc.SetFocus()
4694
4695 if self._TCTRL_description.GetValue().strip() == '':
4696 validity = False
4697 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False)
4698 self._TCTRL_description.SetFocus()
4699 else:
4700 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True)
4701
4702 return validity
4703
4704 #----------------------------------------------------------------
4706 data = gmPathLab.create_test_panel(description = self._TCTRL_description.GetValue().strip())
4707 data['comment'] = self._TCTRL_comment.GetValue().strip()
4708 data.save()
4709 if self.__loincs is not None:
4710 data.included_loincs = self.__loincs
4711 self.data = data
4712 return True
4713
4714 #----------------------------------------------------------------
4716 self.data['description'] = self._TCTRL_description.GetValue().strip()
4717 self.data['comment'] = self._TCTRL_comment.GetValue().strip()
4718 self.data.save()
4719 if self.__loincs is not None:
4720 self.data.included_loincs = self.__loincs
4721 return True
4722
4723 #----------------------------------------------------------------
4725 self._TCTRL_description.SetValue('')
4726 self._TCTRL_comment.SetValue('')
4727 self._PRW_loinc.SetText('', None)
4728 self._LBL_loinc.SetLabel('')
4729 self.__loincs = None
4730 self.__refresh_loinc_list()
4731
4732 self._TCTRL_description.SetFocus()
4733
4734 #----------------------------------------------------------------
4737
4738 #----------------------------------------------------------------
4740 self._TCTRL_description.SetValue(self.data['description'])
4741 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
4742 self._PRW_loinc.SetText('', None)
4743 self._LBL_loinc.SetLabel('')
4744 self.__loincs = self.data['loincs']
4745 self.__refresh_loinc_list()
4746
4747 self._PRW_loinc.SetFocus()
4748
4749 #----------------------------------------------------------------
4750 # event handlers
4751 #----------------------------------------------------------------
4753 loinc = self._PRW_loinc.GetData()
4754 if loinc is None:
4755 self._LBL_loinc.SetLabel('')
4756 return
4757 loinc_detail = gmLOINC.loinc2data(loinc = loinc)
4758 if loinc_detail is None:
4759 loinc_str = _('no LOINC details found')
4760 else:
4761 loinc_str = '%s: %s%s' % (
4762 loinc,
4763 loinc_detail['term'],
4764 gmTools.coalesce(loinc_detail['example_units'], '', ' (%s)')
4765 )
4766 self._LBL_loinc.SetLabel(loinc_str)
4767
4768 #----------------------------------------------------------------
4790
4791 #----------------------------------------------------------------
4795
4796 #----------------------------------------------------------------
4798 loincs2remove = self._LCTRL_loincs.selected_item_data
4799 if loincs2remove is None:
4800 return
4801 for loinc in loincs2remove:
4802 try:
4803 while True:
4804 self.__loincs.remove(loinc[0])
4805 except ValueError:
4806 pass
4807 self.__refresh_loinc_list()
4808
4809 #================================================================
4810 # main
4811 #----------------------------------------------------------------
4812 if __name__ == '__main__':
4813
4814 from Gnumed.pycommon import gmLog2
4815 from Gnumed.wxpython import gmPatSearchWidgets
4816
4817 gmI18N.activate_locale()
4818 gmI18N.install_domain()
4819 gmDateTime.init()
4820
4821 #------------------------------------------------------------
4823 pat = gmPersonSearch.ask_for_patient()
4824 app = wx.PyWidgetTester(size = (500, 300))
4825 lab_grid = cMeasurementsGrid(app.frame, -1)
4826 lab_grid.patient = pat
4827 app.frame.Show()
4828 app.MainLoop()
4829 #------------------------------------------------------------
4831 pat = gmPersonSearch.ask_for_patient()
4832 gmPatSearchWidgets.set_active_patient(patient=pat)
4833 app = wx.PyWidgetTester(size = (500, 300))
4834 ea = cMeasurementEditAreaPnl(app.frame, -1)
4835 app.frame.Show()
4836 app.MainLoop()
4837 #------------------------------------------------------------
4838 # def test_primary_care_vitals_pnl():
4839 # app = wx.PyWidgetTester(size = (500, 300))
4840 # pnl = wxgPrimaryCareVitalsInputPnl.wxgPrimaryCareVitalsInputPnl(app.frame, -1)
4841 # app.frame.Show()
4842 # app.MainLoop()
4843 #------------------------------------------------------------
4844 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
4845 #test_grid()
4846 test_test_ea_pnl()
4847 #test_primary_care_vitals_pnl()
4848
4849 #================================================================
4850
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Feb 29 02:55:27 2020 | http://epydoc.sourceforge.net |