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