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