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