1 """GNUmed medication handling widgets."""
2
3
4 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
5 __license__ = "GPL v2 or later"
6
7 import logging
8 import sys
9 import datetime as pydt
10
11
12 import wx
13 import wx.grid
14
15
16 if __name__ == '__main__':
17 sys.path.insert(0, '../../')
18 from Gnumed.pycommon import gmI18N
19 gmI18N.activate_locale()
20 gmI18N.install_domain(domain = 'gnumed')
21
22 from Gnumed.pycommon import gmDispatcher
23 from Gnumed.pycommon import gmCfg
24 from Gnumed.pycommon import gmTools
25 from Gnumed.pycommon import gmDateTime
26 from Gnumed.pycommon import gmMatchProvider
27 from Gnumed.pycommon import gmI18N
28 from Gnumed.pycommon import gmPrinting
29 from Gnumed.pycommon import gmCfg2
30 from Gnumed.pycommon import gmNetworkTools
31
32 from Gnumed.business import gmPerson
33 from Gnumed.business import gmPraxis
34 from Gnumed.business import gmMedication
35 from Gnumed.business import gmForms
36 from Gnumed.business import gmStaff
37 from Gnumed.business import gmDocuments
38 from Gnumed.business import gmLOINC
39 from Gnumed.business import gmClinicalRecord
40 from Gnumed.business import gmClinicalCalculator
41 from Gnumed.business import gmPathLab
42
43 from Gnumed.wxpython import gmGuiHelpers
44 from Gnumed.wxpython import gmRegetMixin
45 from Gnumed.wxpython import gmAuthWidgets
46 from Gnumed.wxpython import gmEditArea
47 from Gnumed.wxpython import gmMacro
48 from Gnumed.wxpython import gmCfgWidgets
49 from Gnumed.wxpython import gmListWidgets
50 from Gnumed.wxpython import gmPhraseWheel
51 from Gnumed.wxpython import gmFormWidgets
52 from Gnumed.wxpython import gmAllergyWidgets
53 from Gnumed.wxpython import gmDocumentWidgets
54 from Gnumed.wxpython import gmSubstanceMgmtWidgets
55
56
57 _log = logging.getLogger('gm.ui')
58
59
60
61
63
65
66 query = """
67 SELECT DISTINCT ON (list_label)
68 preparation AS data,
69 preparation AS list_label,
70 preparation AS field_label
71 FROM ref.drug_product
72 WHERE preparation %(fragment_condition)s
73 ORDER BY list_label
74 LIMIT 30"""
75 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
76 mp.setThresholds(1, 2, 4)
77 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
78 self.SetToolTip(_('The preparation (form) of the substance or product.'))
79 self.matcher = mp
80 self.selection_only = False
81
82
83
84
102
103
105
118
119
121 entry_type, pk = self.GetData(as_instance = False, can_create = False)
122 if entry_type == 1:
123 return gmMedication.cDrugProduct(aPK_obj = pk)
124 if entry_type == 2:
125 return gmMedication.cConsumableSubstance(aPK_obj = pk)
126 if entry_type == 3:
127 return gmMedication.cDrugComponent(aPK_obj = pk)
128 raise ValueError('entry type must be 1=drug product or 2=substance or 3=component')
129
130
132
134
135 query = """
136 SELECT DISTINCT ON (sched)
137 schedule as sched,
138 schedule
139 FROM clin.substance_intake
140 WHERE schedule %(fragment_condition)s
141 ORDER BY sched
142 LIMIT 50"""
143
144 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
145 mp.setThresholds(1, 2, 4)
146 mp.word_separators = '[ \t=+&:@]+'
147 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
148 self.SetToolTip(_('The schedule for taking this substance.'))
149 self.matcher = mp
150 self.selection_only = False
151
152
154
156
157 query = """
158 (
159 SELECT DISTINCT ON (field_label)
160 aim
161 AS data,
162 aim || ' (' || substance || ' ' || amount || ' ' || unit || ')'
163 AS list_label,
164 aim
165 AS field_label
166 FROM clin.v_substance_intakes
167 WHERE
168 aim %(fragment_condition)s
169 %(ctxt_substance)s
170 ) UNION (
171 SELECT DISTINCT ON (field_label)
172 aim
173 AS data,
174 aim || ' (' || substance || ' ' || amount || ' ' || unit || ')'
175 AS list_label,
176 aim
177 AS field_label
178 FROM clin.v_substance_intakes
179 WHERE
180 aim %(fragment_condition)s
181 )
182 ORDER BY list_label
183 LIMIT 30"""
184
185 context = {'ctxt_substance': {
186 'where_part': 'AND substance = %(substance)s',
187 'placeholder': 'substance'
188 }}
189
190 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = context)
191 mp.setThresholds(1, 2, 4)
192
193 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
194 self.SetToolTip(_('The medical aim for consuming this substance.'))
195 self.matcher = mp
196 self.selection_only = False
197
198
200
201 if intake['is_currently_active']:
202 intake['discontinued'] = gmDateTime.pydt_now_here()
203 if intake['discontinue_reason'] is None:
204 intake['discontinue_reason'] = '%s %s' % (_('not tolerated:'), _('discontinued due to allergy or intolerance'))
205 else:
206 if not intake['discontinue_reason'].startswith(_('not tolerated:')):
207 intake['discontinue_reason'] = '%s %s' % (_('not tolerated:'), intake['discontinue_reason'])
208 if not intake.save():
209 return False
210
211 allg = intake.turn_into_allergy(encounter_id = emr.active_encounter['pk_encounter'])
212
213 drug = intake.containing_drug
214 comps = [ c['substance'] for c in drug.components ]
215 if len(comps) > 1:
216 gmGuiHelpers.gm_show_info (
217 aTitle = _('Documented an allergy'),
218 aMessage = _(
219 'An allergy was documented against the substance:\n'
220 '\n'
221 ' [%s]\n'
222 '\n'
223 'This substance was taken with the multi-component drug product:\n'
224 '\n'
225 ' [%s (%s)]\n'
226 '\n'
227 'Note that ALL components of this product were discontinued.'
228 ) % (
229 intake['substance'],
230 intake['product'],
231 ' & '.join(comps)
232 )
233 )
234
235 if parent is None:
236 parent = wx.GetApp().GetTopWindow()
237
238 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent, -1)
239 dlg.ShowModal()
240
241 return True
242
243
245
246 if parent is None:
247 parent = wx.GetApp().GetTopWindow()
248
249 if emr is None:
250 emr = gmPerson.gmCurrentPatient().emr
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270 def get_tooltip(intake=None):
271 return intake.format(single_line = False, show_all_product_components = True)
272
273 def refresh(lctrl):
274 intakes = emr.get_current_medications (
275 include_inactive = False,
276 include_unapproved = True,
277 order_by = 'substance, product, started'
278 )
279 items = []
280 for i in intakes:
281 started = i.medically_formatted_start
282 items.append ([
283 '%s%s %s %s %s%s' % (
284 i['substance'],
285 gmTools.coalesce(i['product'], '', ' (%s)'),
286 i['amount'],
287 i['unit'],
288 i['l10n_preparation'],
289 gmTools.coalesce(i['external_code_product'], '', ' [%s::%s]' % (i['external_code_type_product'], i['external_code_product']))
290 ),
291 '%s%s%s' % (
292 started,
293 gmTools.coalesce(i['schedule'], '', ' %%s %s' % gmTools.u_arrow2right),
294 gmTools.coalesce(i['duration'], '', ' %s')
295 ),
296 '%s' % (
297 gmTools.bool2subst (
298 i['intake_is_approved_of'],
299 '',
300 _('disapproved')
301 )
302 )
303 ])
304 lctrl.set_string_items(items)
305 lctrl.set_data(intakes)
306
307
308 return gmListWidgets.get_choices_from_list (
309 parent = parent,
310 caption = _('Substances consumed by the patient'),
311 columns = [ _('Intake'), _('Application'), _('Status') ],
312 single_selection = False,
313
314
315
316 refresh_callback = refresh,
317 list_tooltip_callback = get_tooltip
318
319 )
320
321
322 from Gnumed.wxGladeWidgets import wxgCurrentMedicationEAPnl
323
324 -class cSubstanceIntakeEAPnl(wxgCurrentMedicationEAPnl.wxgCurrentMedicationEAPnl, gmEditArea.cGenericEditAreaMixin):
325
345
346
358
359
361
362 curr_pat = gmPerson.gmCurrentPatient()
363 emr = curr_pat.emr
364
365
366 state = emr.allergy_state
367 if state['last_confirmed'] is None:
368 confirmed = _('never')
369 else:
370 confirmed = gmDateTime.pydt_strftime(state['last_confirmed'], '%Y %b %d')
371 msg = _('%s, last confirmed %s\n') % (state.state_string, confirmed)
372 msg += gmTools.coalesce(state['comment'], '', _('Comment (%s): %%s\n') % state['modified_by'])
373 tooltip = ''
374 allgs = emr.get_allergies()
375 if len(allgs) > 0:
376 msg += '\n'
377 for allergy in allgs:
378 msg += '%s: %s (%s)\n' % (
379 allergy['descriptor'],
380 allergy['l10n_type'],
381 gmTools.bool2subst(allergy['definite'], _('definite'), _('suspected'), '?')
382 )
383 tooltip += '%s: %s\n' % (
384 allergy['descriptor'],
385 gmTools.coalesce(allergy['reaction'], _('reaction not recorded'))
386 )
387 if len(allgs) > 0:
388 msg += '\n'
389 tooltip += '\n'
390 del allgs
391
392
393 abuses = emr.abused_substances
394 for abuse in abuses:
395 tooltip += abuse.format(single_line = False, include_metadata = False)
396 tooltip += '\n'
397 if abuse['harmful_use_type'] in [None, 0]:
398 continue
399 msg += abuse.format(single_line = True)
400 msg += '\n'
401 if len(abuses) > 0:
402 msg += '\n'
403 tooltip += '\n'
404 del abuses
405
406
407 gfrs = emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, max_no_of_results = 1)
408 if len(gfrs) == 0:
409 self.calc.patient = curr_pat
410 gfr = self.calc.eGFR
411 if gfr.numeric_value is None:
412 msg += _('GFR: unknown')
413 else:
414 msg += gfr.message
415 egfrs = self.calc.eGFRs
416 tts = []
417 for egfr in egfrs:
418 if egfr.numeric_value is None:
419 continue
420 tts.append(egfr.format (
421 left_margin = 0,
422 width = 50,
423 eol = '\n',
424 with_formula = False,
425 with_warnings = True,
426 with_variables = False,
427 with_sub_results = False,
428 return_list = False
429 ))
430 tooltip += '\n'.join(tts)
431 else:
432 gfr = gfrs[0]
433 msg += '%s: %s %s (%s)\n' % (
434 gfr['unified_abbrev'],
435 gfr['unified_val'],
436 gmTools.coalesce(gfr['abnormality_indicator'], '', ' (%s)'),
437 gmDateTime.pydt_strftime (
438 gfr['clin_when'],
439 format = '%Y %b %d'
440 )
441 )
442 tooltip += _('GFR reported by path lab')
443
444
445 edc = emr.EDC
446 if edc is not None:
447 msg += '\n\n'
448 if emr.EDC_is_fishy:
449 msg += _('EDC (!?!): %s') % gmDateTime.pydt_strftime(edc, format = '%Y %b %d')
450 tooltip += _(
451 'The Expected Date of Confinement is rather questionable.\n'
452 '\n'
453 'Please check patient age, patient gender, time until/since EDC.'
454 )
455 else:
456 msg += _('EDC: %s') % gmDateTime.pydt_strftime(edc, format = '%Y %b %d')
457
458 self._LBL_allergies.SetLabel(msg)
459 self._LBL_allergies.SetToolTip(tooltip)
460
461
463
464 drug = self._PRW_drug.GetData(as_instance = True)
465 if drug is None:
466 self._LBL_drug_details.SetLabel('')
467 self._LBL_drug_details.SetToolTip('')
468 self.Layout()
469 return
470
471 if len(drug['components']) == 0:
472 comps = _('<no components>')
473 else:
474 comps = '\n'.join ([
475 ' %s %s%s%s' % (
476 c['substance'],
477 c['amount'],
478 c['unit'],
479 gmTools.coalesce(c['dose_unit'], '', '/%s')
480 )
481 for c in drug['components']
482 ])
483 self._LBL_drug_details.SetLabel('%s\n%s' % (drug['product'], comps))
484 self._LBL_drug_details.SetToolTip(drug.format())
485 self.Layout()
486 return
487
488
489
490
492
493 self._PRW_drug.display_as_valid(True)
494
495
496 if self.mode == 'edit':
497 return True
498
499 selected_drug = self._PRW_drug.GetData(as_instance = True)
500
501
502 if selected_drug is None:
503 val = self._PRW_drug.GetValue().strip()
504 if val == '':
505 self._PRW_drug.display_as_valid(False)
506 self._PRW_drug.SetFocus()
507 return False
508
509 drug = gmSubstanceMgmtWidgets.edit_single_component_generic_drug (
510 parent = self,
511 drug = None,
512 single_entry = True,
513 fields = {'substance': {'value': val, 'data': None}},
514 return_drug = True
515 )
516 if drug is None:
517 self._PRW_drug.display_as_valid(False)
518 self._PRW_drug.SetFocus()
519 return False
520 comp = drug.components[0]
521 self._PRW_drug.SetText (
522 _('%s w/ %s%s%s of %s') % (
523 comp['product'],
524 comp['amount'],
525 comp['unit'],
526 gmTools.coalesce(comp['dose_unit'], '', '/%s'),
527 comp['substance']
528 ),
529 drug['pk_drug_product']
530 )
531 selected_drug = drug
532 self.__refresh_drug_details()
533 self._PRW_drug.display_as_valid(True)
534 self._PRW_drug.SetFocus()
535
536
537
538 return False
539
540
541 if selected_drug.exists_as_intake(pk_patient = gmPerson.gmCurrentPatient().ID):
542 title = _('Adding substance intake entry')
543 msg = _(
544 'The patient is already taking\n'
545 '\n'
546 ' %s\n'
547 '\n'
548 'You will want to adjust the schedule\n'
549 'rather than document the intake twice.'
550 ) % self._PRW_drug.GetValue().strip()
551 gmGuiHelpers.gm_show_warning(aTitle = title, aMessage = msg)
552 self._PRW_drug.display_as_valid(False)
553 self._PRW_drug.SetFocus()
554 return False
555
556 self._PRW_drug.display_as_valid(True)
557 return True
558
559
561
562 validity = self._check_drug_is_valid()
563
564
565 if self._CHBOX_approved.IsChecked():
566 if self._PRW_episode.GetValue().strip() == '':
567 self._PRW_episode.display_as_valid(False)
568 validity = False
569 else:
570 self._PRW_episode.display_as_valid(True)
571
572 if self._PRW_duration.GetValue().strip() in ['', gmTools.u_infinity]:
573 self._PRW_duration.display_as_valid(True)
574 else:
575 if self._PRW_duration.GetData() is None:
576
577 if gmDateTime.str2interval(self._PRW_duration.GetValue()) is None:
578 self._PRW_duration.display_as_valid(False)
579 validity = False
580
581 else:
582 self._PRW_duration.display_as_valid(True)
583
584 else:
585 self._PRW_duration.display_as_valid(True)
586
587
588 started = None
589 if self._CHBOX_start_unknown.IsChecked() is False:
590 started = self._DP_started.GetData()
591 if started is None:
592 self._DP_started.display_as_valid(False)
593 self._DP_started.SetFocus()
594 validity = False
595 else:
596 self._DP_started.display_as_valid(True)
597
598 if validity is False:
599 self.StatusText = _('Input incomplete/invalid for saving as substance intake.')
600
601
602 discontinued = self._DP_discontinued.GetData()
603 if discontinued is not None:
604 now = gmDateTime.pydt_now_here().replace (
605 hour = 23,
606 minute = 59,
607 second = 59,
608 microsecond = 111111
609 )
610
611 if discontinued > now:
612 self._DP_discontinued.display_as_valid(False)
613 validity = False
614 self.StatusText = _('Discontinued (%s) in the future (now: %s)!') % (discontinued, now)
615 else:
616 if started is not None:
617 started = started.replace (
618 hour = 0,
619 minute = 0,
620 second = 0,
621 microsecond = 1
622 )
623
624 if started > discontinued:
625 self._DP_started.display_as_valid(False)
626 self._DP_discontinued.display_as_valid(False)
627 validity = False
628 self.StatusText = _('Discontinued (%s) before started (%s) !') % (discontinued, started)
629 else:
630 self._DP_started.display_as_valid(True)
631 self._DP_discontinued.display_as_valid(True)
632
633 return validity
634
635
637
638 epi = self._PRW_episode.GetData()
639 if epi is None:
640
641 epi = self._PRW_episode.GetData(can_create = True, is_open = True)
642
643 selected_drug = self._PRW_drug.GetData(as_instance = True)
644 intake = selected_drug.turn_into_intake (
645 encounter = gmPerson.gmCurrentPatient().emr.current_encounter['pk_encounter'],
646 episode = epi
647 )
648
649 if intake is None:
650 self.StatusText = _('Cannot add duplicate of (maybe inactive) substance intake.')
651 return False
652
653 intake['started'] = self._DP_started.GetData()
654 if self._CHBOX_start_unknown.IsChecked():
655 intake['comment_on_start'] = '?'
656 else:
657 intake['comment_on_start'] = self._PRW_start_certainty.GetValue().strip()
658 intake['discontinued'] = self._DP_discontinued.GetData()
659 if intake['discontinued'] is None:
660 intake['discontinue_reason'] = None
661 else:
662 intake['discontinue_reason'] = self._PRW_discontinue_reason.GetValue().strip()
663 intake['schedule'] = self._PRW_schedule.GetValue().strip()
664 intake['aim'] = self._PRW_aim.GetValue().strip()
665 intake['notes'] = self._PRW_notes.GetValue().strip()
666 intake['is_long_term'] = self._CHBOX_long_term.IsChecked()
667 intake['intake_is_approved_of'] = self._CHBOX_approved.IsChecked()
668 if self._PRW_duration.GetValue().strip() in ['', gmTools.u_infinity]:
669 intake['duration'] = None
670 else:
671 if self._PRW_duration.GetData() is None:
672 intake['duration'] = gmDateTime.str2interval(self._PRW_duration.GetValue())
673 else:
674 intake['duration'] = self._PRW_duration.GetData()
675 intake.save()
676
677 self.data = intake
678
679 return True
680
681
683
684
685 self.data['started'] = self._DP_started.GetData()
686 if self._CHBOX_start_unknown.IsChecked():
687 self.data['comment_on_start'] = '?'
688 else:
689 self.data['comment_on_start'] = self._PRW_start_certainty.GetValue().strip()
690 self.data['discontinued'] = self._DP_discontinued.GetData()
691 if self.data['discontinued'] is None:
692 self.data['discontinue_reason'] = None
693 else:
694 self.data['discontinue_reason'] = self._PRW_discontinue_reason.GetValue().strip()
695 self.data['schedule'] = self._PRW_schedule.GetValue()
696 self.data['is_long_term'] = self._CHBOX_long_term.IsChecked()
697 self.data['intake_is_approved_of'] = self._CHBOX_approved.IsChecked()
698 if self._PRW_duration.GetValue().strip() in ['', gmTools.u_infinity]:
699 self.data['duration'] = None
700 else:
701 if self._PRW_duration.GetData() is None:
702 self.data['duration'] = gmDateTime.str2interval(self._PRW_duration.GetValue())
703 else:
704 self.data['duration'] = self._PRW_duration.GetData()
705
706
707 self.data['aim'] = self._PRW_aim.GetValue()
708 self.data['notes'] = self._PRW_notes.GetValue()
709 epi = self._PRW_episode.GetData()
710 if epi is None:
711
712 epi = self._PRW_episode.GetData(can_create = True, is_open = True)
713 self.data['pk_episode'] = epi
714
715 self.data.save()
716
717 return True
718
719
721 self._PRW_drug.SetText('', None)
722
723 self._PRW_schedule.SetText('', None)
724 self._PRW_duration.SetText('', None)
725 self._PRW_aim.SetText('', None)
726 self._PRW_notes.SetText('', None)
727 self._PRW_episode.SetText('', None)
728
729 self._CHBOX_long_term.SetValue(False)
730 self._CHBOX_approved.SetValue(True)
731
732 self._CHBOX_start_unknown.SetValue(False)
733 self._DP_started.SetData(gmDateTime.pydt_now_here())
734 self._DP_started.Enable(True)
735 self._PRW_start_certainty.SetText('', None)
736 self._PRW_start_certainty.Enable(True)
737 self._DP_discontinued.SetData(None)
738 self._PRW_discontinue_reason.SetValue('')
739 self._PRW_discontinue_reason.Enable(False)
740
741 self.__refresh_drug_details()
742 self.__refresh_precautions()
743
744 self._PRW_drug.SetFocus()
745
746
748
749 self._PRW_drug.SetText (
750 _('%s w/ %s%s%s of %s') % (
751 self.data['product'],
752 self.data['amount'],
753 self.data['unit'],
754 gmTools.coalesce(self.data['dose_unit'], '', '/%s'),
755 self.data['substance']
756 ),
757 self.data['pk_drug_product']
758 )
759
760 self._PRW_drug.Disable()
761
762 if self.data['is_long_term']:
763 self._CHBOX_long_term.SetValue(True)
764 self._PRW_duration.Enable(False)
765 self._PRW_duration.SetText(gmTools.u_infinity, None)
766 self._BTN_discontinued_as_planned.Enable(False)
767 else:
768 self._CHBOX_long_term.SetValue(False)
769 self._PRW_duration.Enable(True)
770 self._BTN_discontinued_as_planned.Enable(True)
771 self._PRW_duration.SetData(self.data['duration'])
772 self._PRW_aim.SetText(gmTools.coalesce(self.data['aim'], ''), self.data['aim'])
773 self._PRW_notes.SetText(gmTools.coalesce(self.data['notes'], ''), self.data['notes'])
774 self._PRW_episode.SetData(self.data['pk_episode'])
775 self._PRW_schedule.SetText(gmTools.coalesce(self.data['schedule'], ''), self.data['schedule'])
776
777 self._CHBOX_approved.SetValue(self.data['intake_is_approved_of'])
778
779 self._DP_started.SetData(self.data['started'])
780 self._PRW_start_certainty.SetText(self.data['comment_on_start'], None)
781 if self.data['start_is_unknown']:
782 self._CHBOX_start_unknown.SetValue(True)
783 self._DP_started.Enable(False)
784 self._PRW_start_certainty.Enable(False)
785 else:
786 self._CHBOX_start_unknown.SetValue(False)
787 self._DP_started.Enable(True)
788 self._PRW_start_certainty.Enable(True)
789
790 self._DP_discontinued.SetData(self.data['discontinued'])
791 self._PRW_discontinue_reason.SetValue(gmTools.coalesce(self.data['discontinue_reason'], ''))
792 if self.data['discontinued'] is not None:
793 self._PRW_discontinue_reason.Enable()
794
795 self.__refresh_drug_details()
796 self.__refresh_precautions()
797
798 self._PRW_schedule.SetFocus()
799
800
802 self._refresh_as_new()
803
804 self._PRW_episode.SetData(self.data['pk_episode'])
805 self._DP_started.SetData(self.data['started'])
806
807 self._PRW_drug.SetFocus()
808
809
810
811
813 self.__refresh_drug_details()
814
815
817 drug = self._PRW_drug.GetData(as_instance = True)
818 if drug is None:
819 self._PRW_aim.unset_context(context = 'substance')
820 return
821
822
823
824
825
827 if self._DP_discontinued.GetData() is None:
828 self._PRW_discontinue_reason.Enable(False)
829 else:
830 self._PRW_discontinue_reason.Enable(True)
831
832
835
836
839
840
843
844
847
848
851
852
860
861
891
892
894 if self._CHBOX_long_term.IsChecked() is True:
895 self._PRW_duration.Enable(False)
896 self._BTN_discontinued_as_planned.Enable(False)
897 self._PRW_discontinue_reason.Enable(False)
898 else:
899 self._PRW_duration.Enable(True)
900 self._BTN_discontinued_as_planned.Enable(True)
901 self._PRW_discontinue_reason.Enable(True)
902
903 self.__refresh_precautions()
904
905
907 event.Skip()
908 if self._CHBOX_start_unknown.IsChecked() is True:
909 self._DP_started.Enable(False)
910 self._PRW_start_certainty.Enable(False)
911 else:
912 self._DP_started.Enable(True)
913 self._PRW_start_certainty.Enable(True)
914
915 self.__refresh_precautions()
916
917
927
928
930
931 comps = intake.containing_drug.components
932 if len(comps) > 1:
933 msg = _(
934 'This intake is part of a multi-component drug product:\n'
935 '\n'
936 ' %s\n'
937 '\n'
938 'Really delete all intakes related to this drug product ?'
939 ) % '\n '.join (
940 [ '%s %s%s' % (c['substance'], c['amount'], c.formatted_units) for c in comps ]
941 )
942 delete_all = gmGuiHelpers.gm_show_question (
943 title = _('Deleting medication / substance intake'),
944 question = msg
945 )
946 if not delete_all:
947 return
948
949 msg = _(
950 '\n'
951 '[%s]\n'
952 '\n'
953 'It may be prudent to edit (before deletion) the details\n'
954 'of this substance intake entry so as to leave behind\n'
955 'some indication of why it was deleted.\n'
956 ) % intake.format()
957
958 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
959 parent,
960 -1,
961 caption = _('Deleting medication / substance intake'),
962 question = msg,
963 button_defs = [
964 {'label': _('&Edit'), 'tooltip': _('Allow editing of substance intake entry before deletion.'), 'default': True},
965 {'label': _('&Delete'), 'tooltip': _('Delete immediately without editing first.')},
966 {'label': _('&Cancel'), 'tooltip': _('Abort. Do not delete or edit substance intake entry.')}
967 ]
968 )
969
970 edit_first = dlg.ShowModal()
971 dlg.DestroyLater()
972
973 if edit_first == wx.ID_CANCEL:
974 return
975
976 if edit_first == wx.ID_YES:
977 edit_intake_of_substance(parent = parent, substance = intake)
978 delete_it = gmGuiHelpers.gm_show_question (
979 aMessage = _('Now delete substance intake entry ?'),
980 aTitle = _('Deleting medication / substance intake')
981 )
982 else:
983 delete_it = True
984
985 if not delete_it:
986 return
987
988 gmMedication.delete_substance_intake(pk_intake = intake['pk_substance_intake'], delete_siblings = True)
989
990
992 ea = cSubstanceIntakeEAPnl(parent, -1, substance = substance)
993 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
994 dlg.SetTitle(gmTools.coalesce(substance, _('Adding medication/non-medication substance intake'), _('Editing medication/non-medication substance intake')))
995 dlg.left_extra_button = (
996 _('Allergy'),
997 _('Document an allergy against this substance.'),
998 ea.turn_into_allergy
999 )
1000 dlg.SetSize((650,500))
1001 if dlg.ShowModal() == wx.ID_OK:
1002 dlg.DestroyLater()
1003 return True
1004 dlg.DestroyLater()
1005 return False
1006
1007
1008
1009
1037
1038
1040
1041 if parent is None:
1042 parent = wx.GetApp().GetTopWindow()
1043
1044
1045 dbcfg = gmCfg.cCfgSQL()
1046 option = 'form_templates.medication_list'
1047
1048 template = dbcfg.get2 (
1049 option = option,
1050 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
1051 bias = 'user'
1052 )
1053
1054 if template is None:
1055 template = configure_medication_list_template(parent = parent)
1056 if template is None:
1057 gmGuiHelpers.gm_show_error (
1058 aMessage = _('There is no medication list template configured.'),
1059 aTitle = _('Printing medication list')
1060 )
1061 return False
1062 else:
1063 try:
1064 name, ver = template.split(' - ')
1065 except Exception:
1066 _log.exception('problem splitting medication list template name [%s]', template)
1067 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading medication list template.'), beep = True)
1068 return False
1069 template = gmForms.get_form_template(name_long = name, external_version = ver)
1070 if template is None:
1071 gmGuiHelpers.gm_show_error (
1072 aMessage = _('Cannot load medication list template [%s - %s]') % (name, ver),
1073 aTitle = _('Printing medication list')
1074 )
1075 return False
1076
1077
1078 meds_list = gmFormWidgets.generate_form_from_template (
1079 parent = parent,
1080 template = template,
1081 edit = False
1082 )
1083 if meds_list is None:
1084 return False
1085
1086
1087 return gmFormWidgets.act_on_generated_forms (
1088 parent = parent,
1089 forms = [meds_list],
1090 jobtype = 'medication_list',
1091
1092 episode_name = gmMedication.DEFAULT_MEDICATION_HISTORY_EPISODE,
1093 progress_note = _('generated medication list document'),
1094 review_copy_as_normal = True
1095 )
1096
1097
1126
1127
1129
1130 if parent is None:
1131 parent = wx.GetApp().GetTopWindow()
1132
1133 dbcfg = gmCfg.cCfgSQL()
1134 option = 'form_templates.prescription'
1135 template_name = dbcfg.get2 (
1136 option = option,
1137 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
1138 bias = 'user'
1139 )
1140
1141 if template_name is None:
1142 template = configure_prescription_template(parent = parent)
1143 if template is None:
1144 gmGuiHelpers.gm_show_error (
1145 aMessage = _('There is no prescription template configured.'),
1146 aTitle = _('Printing prescription')
1147 )
1148 return None
1149 return template
1150
1151 try:
1152 name, ver = template_name.split(' - ')
1153 except Exception:
1154 _log.exception('problem splitting prescription template name [%s]', template_name)
1155 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading prescription template.'), beep = True)
1156 return False
1157 template = gmForms.get_form_template(name_long = name, external_version = ver)
1158 if template is None:
1159 gmGuiHelpers.gm_show_error (
1160 aMessage = _('Cannot load prescription template [%s - %s]') % (name, ver),
1161 aTitle = _('Printing prescription')
1162 )
1163 return None
1164 return template
1165
1166
1193
1194
1222
1223
1225
1226 if len(prescribed_drugs) == 0:
1227 return
1228
1229 curr_meds = [ i['pk_drug_product'] for i in emr.get_current_medications() if i['pk_drug_product'] is not None ]
1230 new_drugs = []
1231 for drug in prescribed_drugs:
1232 if drug['pk_drug_product'] not in curr_meds:
1233 new_drugs.append(drug)
1234
1235 if len(new_drugs) == 0:
1236 return
1237
1238 if parent is None:
1239 parent = wx.GetApp().GetTopWindow()
1240
1241 picker = gmListWidgets.cItemPickerDlg (
1242 parent,
1243 -1,
1244 msg = _(
1245 'These products have been prescribed but are not listed\n'
1246 'in the current medication list of this patient.\n'
1247 '\n'
1248 'Please select those you want added to the medication list.'
1249 )
1250 )
1251 picker.set_columns (
1252 columns = [_('Newly prescribed drugs')],
1253 columns_right = [_('Add to medication list')]
1254 )
1255 choices = [ ('%s %s (%s)' % (d['product'], d['l10n_preparation'], '; '.join(d['components']))) for d in new_drugs ]
1256 picker.set_choices (
1257 choices = choices,
1258 data = new_drugs
1259 )
1260 picker.ShowModal()
1261 drugs2add = picker.get_picks()
1262 picker.DestroyLater()
1263
1264 if drugs2add is None:
1265 return
1266
1267 if len(drugs2add) == 0:
1268 return
1269
1270 for drug in drugs2add:
1271
1272 intake = emr.add_substance_intake(pk_component = drug['components'][0]['pk_component'])
1273 if intake is None:
1274 continue
1275 intake['intake_is_approved_of'] = True
1276 intake.save()
1277
1278 return
1279
1280
1282 """A grid class for displaying current substance intake.
1283
1284 - does NOT listen to the currently active patient
1285 - thereby it can display any patient at any time
1286 """
1288
1289 wx.grid.Grid.__init__(self, *args, **kwargs)
1290
1291 self.__patient = None
1292 self.__row_data = {}
1293 self.__prev_row = None
1294 self.__prev_tooltip_row = None
1295 self.__prev_cell_0 = None
1296 self.__grouping_mode = 'issue'
1297 self.__filter_show_unapproved = True
1298 self.__filter_show_inactive = True
1299
1300 self.__grouping2col_labels = {
1301 'issue': [
1302 _('Health issue'),
1303 _('Substance'),
1304 _('Strength'),
1305 _('Schedule'),
1306 _('Timeframe'),
1307 _('Product'),
1308 _('Advice')
1309 ],
1310 'product': [
1311 _('Product'),
1312 _('Schedule'),
1313 _('Substance'),
1314 _('Strength'),
1315 _('Timeframe'),
1316 _('Health issue'),
1317 _('Advice')
1318 ],
1319 'episode': [
1320 _('Episode'),
1321 _('Substance'),
1322 _('Strength'),
1323 _('Schedule'),
1324 _('Timeframe'),
1325 _('Product'),
1326 _('Advice')
1327 ],
1328 'start': [
1329 _('Episode'),
1330 _('Substance'),
1331 _('Strength'),
1332 _('Schedule'),
1333 _('Timeframe'),
1334 _('Product'),
1335 _('Advice')
1336 ],
1337 }
1338
1339 self.__grouping2order_by_clauses = {
1340 'issue': 'pk_health_issue NULLS FIRST, substance, started',
1341 'episode': 'pk_health_issue NULLS FIRST, episode, substance, started',
1342 'product': 'product NULLS LAST, substance, started',
1343 'start': 'started DESC, substance, episode'
1344 }
1345
1346 self.__init_ui()
1347 self.__register_events()
1348
1349
1350
1351
1353
1354 sel_block_top_left = self.GetSelectionBlockTopLeft()
1355 sel_block_bottom_right = self.GetSelectionBlockBottomRight()
1356 sel_cols = self.GetSelectedCols()
1357 sel_rows = self.GetSelectedRows()
1358
1359 selected_cells = []
1360
1361
1362 selected_cells += self.GetSelectedCells()
1363
1364
1365 selected_cells += list (
1366 (row, col)
1367 for row in sel_rows
1368 for col in range(self.GetNumberCols())
1369 )
1370
1371
1372 selected_cells += list (
1373 (row, col)
1374 for row in range(self.GetNumberRows())
1375 for col in sel_cols
1376 )
1377
1378
1379 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()):
1380 selected_cells += [
1381 (row, col)
1382 for row in range(top_left[0], bottom_right[0] + 1)
1383 for col in range(top_left[1], bottom_right[1] + 1)
1384 ]
1385
1386 return set(selected_cells)
1387
1388
1394
1395
1398
1399
1401 return self.__row_data.values()
1402
1403
1405
1406 self.empty_grid()
1407
1408 if self.__patient is None:
1409 return
1410
1411 emr = self.__patient.emr
1412 meds = emr.get_current_medications (
1413 order_by = self.__grouping2order_by_clauses[self.__grouping_mode],
1414 include_unapproved = self.__filter_show_unapproved,
1415 include_inactive = self.__filter_show_inactive
1416 )
1417 if not meds:
1418 return
1419
1420 self.BeginBatch()
1421
1422
1423 labels = self.__grouping2col_labels[self.__grouping_mode]
1424 if self.__filter_show_unapproved:
1425 self.AppendCols(numCols = len(labels) + 1)
1426 else:
1427 self.AppendCols(numCols = len(labels))
1428 for col_idx in range(len(labels)):
1429 self.SetColLabelValue(col_idx, labels[col_idx])
1430 if self.__filter_show_unapproved:
1431
1432 self.SetColLabelValue(len(labels), '')
1433 self.SetColSize(len(labels), 40)
1434
1435 self.AppendRows(numRows = len(meds))
1436
1437
1438 for row_idx in range(len(meds)):
1439 med = meds[row_idx]
1440 self.__row_data[row_idx] = med
1441
1442 if med['is_currently_active'] is True:
1443 atcs = []
1444 if med['atc_substance'] is not None:
1445 atcs.append(med['atc_substance'])
1446 allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (med['substance'],))
1447 if allg not in [None, False]:
1448 attr = self.GetOrCreateCellAttr(row_idx, 0)
1449 if allg['type'] == 'allergy':
1450 attr.SetTextColour('red')
1451 else:
1452
1453
1454
1455 attr.SetTextColour('magenta')
1456 self.SetRowAttr(row_idx, attr)
1457 else:
1458 attr = self.GetOrCreateCellAttr(row_idx, 0)
1459 attr.SetTextColour('grey')
1460 self.SetRowAttr(row_idx, attr)
1461
1462 if self.__grouping_mode in ['episode', 'start']:
1463 if med['pk_episode'] is None:
1464 self.__prev_cell_0 = None
1465 epi = gmTools.u_diameter
1466 else:
1467 if self.__prev_cell_0 == med['episode']:
1468 epi = ''
1469 else:
1470 self.__prev_cell_0 = med['episode']
1471 epi = gmTools.coalesce(med['episode'], '')
1472 self.SetCellValue(row_idx, 0, gmTools.wrap(text = epi, width = 40))
1473
1474 self.SetCellValue(row_idx, 1, med['substance'])
1475 self.SetCellValue(row_idx, 2, '%s %s' % (med['amount'], med['unit']))
1476 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], ''))
1477 self.SetCellValue(row_idx, 4, med.medically_formatted_start_end)
1478
1479 if med['pk_drug_product'] is None:
1480 product = '%s (%s)' % (gmTools.u_diameter, med['l10n_preparation'])
1481 else:
1482 if med['is_fake_product']:
1483 product = '%s (%s)' % (
1484 gmTools.coalesce(med['product'], '', _('%s <fake>')),
1485 med['l10n_preparation']
1486 )
1487 else:
1488 product = '%s (%s)' % (
1489 gmTools.coalesce(med['product'], ''),
1490 med['l10n_preparation']
1491 )
1492 self.SetCellValue(row_idx, 5, gmTools.wrap(text = product, width = 35))
1493
1494 elif self.__grouping_mode == 'issue':
1495 if med['pk_health_issue'] is None:
1496 self.__prev_cell_0 = None
1497 issue = '%s%s' % (
1498 gmTools.u_diameter,
1499 gmTools.coalesce(med['episode'], '', ' (%s)')
1500 )
1501 else:
1502 if self.__prev_cell_0 == med['health_issue']:
1503 issue = ''
1504 else:
1505 self.__prev_cell_0 = med['health_issue']
1506 issue = med['health_issue']
1507 self.SetCellValue(row_idx, 0, gmTools.wrap(text = issue, width = 40))
1508
1509 self.SetCellValue(row_idx, 1, med['substance'])
1510 self.SetCellValue(row_idx, 2, '%s %s' % (med['amount'], med['unit']))
1511 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], ''))
1512 self.SetCellValue(row_idx, 4, med.medically_formatted_start_end)
1513
1514 if med['pk_drug_product'] is None:
1515 product = '%s (%s)' % (gmTools.u_diameter, med['l10n_preparation'])
1516 else:
1517 if med['is_fake_product']:
1518 product = '%s (%s)' % (
1519 gmTools.coalesce(med['product'], '', _('%s <fake>')),
1520 med['l10n_preparation']
1521 )
1522 else:
1523 product = '%s (%s)' % (
1524 gmTools.coalesce(med['product'], ''),
1525 med['l10n_preparation']
1526 )
1527 self.SetCellValue(row_idx, 5, gmTools.wrap(text = product, width = 35))
1528
1529 elif self.__grouping_mode == 'product':
1530
1531 if med['pk_drug_product'] is None:
1532 self.__prev_cell_0 = None
1533 product = '%s (%s)' % (
1534 gmTools.u_diameter,
1535 med['l10n_preparation']
1536 )
1537 else:
1538 if self.__prev_cell_0 == med['product']:
1539 product = ''
1540 else:
1541 self.__prev_cell_0 = med['product']
1542 if med['is_fake_product']:
1543 product = '%s (%s)' % (
1544 gmTools.coalesce(med['product'], '', _('%s <fake>')),
1545 med['l10n_preparation']
1546 )
1547 else:
1548 product = '%s (%s)' % (
1549 gmTools.coalesce(med['product'], ''),
1550 med['l10n_preparation']
1551 )
1552 self.SetCellValue(row_idx, 0, gmTools.wrap(text = product, width = 35))
1553
1554 self.SetCellValue(row_idx, 1, gmTools.coalesce(med['schedule'], ''))
1555 self.SetCellValue(row_idx, 2, med['substance'])
1556 self.SetCellValue(row_idx, 3, '%s %s' % (med['amount'], med['unit']))
1557 self.SetCellValue(row_idx, 4, med.medically_formatted_start_end)
1558
1559 if med['pk_health_issue'] is None:
1560 issue = '%s%s' % (
1561 gmTools.u_diameter,
1562 gmTools.coalesce(med['episode'], '', ' (%s)')
1563 )
1564 else:
1565 issue = gmTools.coalesce(med['health_issue'], '')
1566 self.SetCellValue(row_idx, 5, gmTools.wrap(text = issue, width = 40))
1567
1568 else:
1569 raise ValueError('unknown grouping mode [%s]' % self.__grouping_mode)
1570
1571 if med['notes'] is not None:
1572 self.SetCellValue(row_idx, 6, gmTools.wrap(text = med['notes'], width = 50))
1573
1574 if self.__filter_show_unapproved:
1575 self.SetCellValue (
1576 row_idx,
1577 len(labels),
1578 gmTools.bool2subst(med['intake_is_approved_of'], gmTools.u_checkmark_thin, gmTools.u_frowning_face, '?')
1579 )
1580 font = self.GetCellFont(row_idx, len(labels))
1581 font.SetPointSize(font.GetPointSize() + 2)
1582 self.SetCellFont(row_idx, len(labels), font)
1583
1584
1585
1586 self.AutoSize()
1587 self.EndBatch()
1588
1590 self.BeginBatch()
1591 self.ClearGrid()
1592
1593
1594 if self.GetNumberRows() > 0:
1595 self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
1596 if self.GetNumberCols() > 0:
1597 self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
1598 self.EndBatch()
1599 self.__row_data = {}
1600 self.__prev_cell_0 = None
1601
1602
1604
1605 if len(self.__row_data) == 0:
1606 return
1607
1608 sel_rows = self.get_selected_rows()
1609 if len(sel_rows) != 1:
1610 return
1611
1612 drug_db = gmSubstanceMgmtWidgets.get_drug_database(patient = self.__patient)
1613 if drug_db is None:
1614 return
1615
1616 intake = self.get_selected_data()[0]
1617 if intake['product'] is None:
1618 drug_db.show_info_on_substance(substance_intake = intake)
1619 else:
1620 drug_db.show_info_on_drug(substance_intake = intake)
1621
1622
1630
1631
1634
1635
1645
1646
1652
1666
1669
1683
1684
1698
1699
1715
1719
1820
1821
1822
1823
1825 self.CreateGrid(0, 1)
1826 self.EnableEditing(0)
1827 self.EnableDragGridSize(1)
1828 self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows)
1829
1830 self.SetColLabelAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTER)
1831
1832 self.SetRowLabelSize(0)
1833 self.SetRowLabelAlignment(horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
1834
1835
1836
1837
1839 return self.__patient
1840
1844
1845 patient = property(_get_patient, _set_patient)
1846
1848 return self.__grouping_mode
1849
1853
1854 grouping_mode = property(_get_grouping_mode, _set_grouping_mode)
1855
1857 return self.__filter_show_unapproved
1858
1862
1863 filter_show_unapproved = property(_get_filter_show_unapproved, _set_filter_show_unapproved)
1864
1866 return self.__filter_show_inactive
1867
1871
1872 filter_show_inactive = property(_get_filter_show_inactive, _set_filter_show_inactive)
1873
1874
1875
1877
1878 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
1879
1880
1881
1882
1883 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
1884
1886 """Calculate where the mouse is and set the tooltip dynamically."""
1887
1888
1889
1890 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904 row, col = self.XYToCell(x, y)
1905
1906 if row == self.__prev_tooltip_row:
1907 return
1908
1909 self.__prev_tooltip_row = row
1910
1911 try:
1912 evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row))
1913 except KeyError:
1914 pass
1915
1920
1921
1940
1941
1953
1954 gmCfgWidgets.configure_string_option (
1955 message = _(
1956 'GNUmed will use this URL to access a website which lets\n'
1957 'you report an adverse drug reaction (ADR).\n'
1958 '\n'
1959 'If you leave this empty it will fall back\n'
1960 'to an URL for reporting ADRs in Germany.'
1961 ),
1962 option = 'external.urls.report_ADR',
1963 bias = 'user',
1964 default_value = gmMedication.URL_drug_adr_german_default,
1965 validator = is_valid
1966 )
1967
1968
1969 from Gnumed.wxGladeWidgets import wxgCurrentSubstancesPnl
1970
1971 -class cCurrentSubstancesPnl(wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl, gmRegetMixin.cRegetOnPaintMixin):
1972
1973 """Panel holding a grid with current substances. Used as notebook page."""
1974
1976
1977 wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl.__init__(self, *args, **kwargs)
1978 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1979
1980 self.__grouping_choice_labels = [
1981 {'label': _('Health issue'), 'data': 'issue'} ,
1982 {'label': _('Drug product'), 'data': 'product'},
1983 {'label': _('Episode'), 'data': 'episode'},
1984 {'label': _('Started'), 'data': 'start'}
1985 ]
1986 self.__lab_panel = None
1987
1988 self.__init_ui()
1989 self.__register_interests()
1990
1991
1993 self._CHCE_grouping.Clear()
1994 for option in self.__grouping_choice_labels:
1995 self._CHCE_grouping.Append(option['label'], option['data'])
1996 self._CHCE_grouping.SetSelection(0)
1997
1998 tt = self._BTN_heart.GetToolTipText()
1999 try:
2000 self._BTN_heart.SetToolTip(tt % gmMedication.URL_long_qt)
2001 except TypeError:
2002 _log.exception('translation error: %s', tt)
2003
2004 tt = self._BTN_kidneys.GetToolTipText()
2005 try:
2006 self._BTN_kidneys.SetToolTip(tt % gmMedication.URL_renal_insufficiency)
2007 except TypeError:
2008 _log.exception('translation error: %s', tt)
2009
2010
2011
2012
2014 """Populate cells with data from model."""
2015 pat = gmPerson.gmCurrentPatient()
2016 if pat.connected:
2017 self._grid_substances.patient = pat
2018 self.__refresh_gfr(pat)
2019 self.__refresh_lab(patient = pat)
2020 else:
2021 self._grid_substances.patient = None
2022 self.__clear_gfr()
2023 self.__refresh_lab(patient = None)
2024 return True
2025
2026
2028
2029 self._GSZR_lab.Clear(True)
2030 self._HLINE_lab.Hide()
2031
2032 if patient is None:
2033 self.Layout()
2034 return
2035
2036 emr = patient.emr
2037 most_recent_results = {}
2038
2039
2040 loincs2monitor = set()
2041 loincs2monitor_data = {}
2042 loinc_max_age = {}
2043 loinc_max_age_str = {}
2044 for intake in self._grid_substances.get_row_data():
2045 for l in intake['loincs']:
2046 loincs2monitor.add(l['loinc'])
2047 loincs2monitor_data[l['loinc']] = {
2048 'substance': intake['substance'],
2049 'comment': l['comment']
2050 }
2051 if l['max_age_in_secs'] is not None:
2052 try:
2053 if loinc_max_age[l['loinc']] > l['max_age_in_secs']:
2054 loinc_max_age[l['loinc']] = l['max_age_in_secs']
2055 loinc_max_age_str[l['loinc']] = l['max_age_str']
2056 except KeyError:
2057 loinc_max_age[l['loinc']] = l['max_age_in_secs']
2058 loinc_max_age_str[l['loinc']] = l['max_age_str']
2059 loincs2monitor_missing = loincs2monitor.copy()
2060 for loinc in loincs2monitor:
2061 results = emr.get_most_recent_results_in_loinc_group(loincs = [loinc], max_no_of_results = 1)
2062 if len(results) == 0:
2063 continue
2064 loincs2monitor_missing.remove(loinc)
2065
2066 result = results[0]
2067 most_recent_results[result['pk_test_result']] = result
2068
2069
2070 if self.__lab_panel is not None:
2071 for result in self.__lab_panel.get_most_recent_results (
2072 pk_patient = patient.ID,
2073 order_by = 'unified_abbrev',
2074 group_by_meta_type = True
2075 ):
2076 try: loincs2monitor_missing.remove(result['loinc_tt'])
2077 except KeyError: pass
2078 try: loincs2monitor_missing.remove(result['loinc_meta'])
2079 except KeyError: pass
2080
2081 most_recent_results[result['pk_test_result']] = result
2082
2083
2084 gfrs = emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, max_no_of_results = 1)
2085 gfr = gfrs[0] if len(gfrs) > 0 else None
2086 creas = emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
2087 crea = creas[0] if len(creas) > 0 else None
2088 edc = emr.EDC
2089
2090
2091 if edc is not None:
2092 if emr.EDC_is_fishy:
2093 lbl = wx.StaticText(self, -1, _('EDC (!?!):'))
2094 val = wx.StaticText(self, -1, gmDateTime.pydt_strftime(edc, format = '%Y %b %d'))
2095 else:
2096 lbl = wx.StaticText(self, -1, _('EDC:'))
2097 val = wx.StaticText(self, -1, gmDateTime.pydt_strftime(edc, format = '%Y %b %d'))
2098 lbl.SetForegroundColour('blue')
2099 szr = wx.BoxSizer(wx.HORIZONTAL)
2100 szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3)
2101 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL)
2102 self._GSZR_lab.Add(szr)
2103
2104
2105 if crea is None:
2106 gfr_3_months_older_than_crea = False
2107 if gfr is not None:
2108 most_recent_results = [gfr] + most_recent_results
2109 elif gfr is None:
2110 gfr_3_months_older_than_crea = True
2111 else:
2112 three_months = pydt.timedelta(weeks = 14)
2113 gfr_3_months_older_than_crea = (crea['clin_when'] - gfr['clin_when']) > three_months
2114 if not gfr_3_months_older_than_crea:
2115 most_recent_results = [gfr] + most_recent_results
2116
2117
2118 now = gmDateTime.pydt_now_here()
2119 if gfr_3_months_older_than_crea:
2120 calc = gmClinicalCalculator.cClinicalCalculator()
2121 calc.patient = patient
2122 gfr = calc.eGFR
2123 if gfr.numeric_value is None:
2124 gfr_msg = '?'
2125 else:
2126 gfr_msg = _('%.1f (%s ago)') % (
2127 gfr.numeric_value,
2128 gmDateTime.format_interval_medically(now - gfr.date_valid)
2129 )
2130 lbl = wx.StaticText(self, -1, _('eGFR:'))
2131 lbl.SetForegroundColour('blue')
2132 val = wx.StaticText(self, -1, gfr_msg)
2133 tts = []
2134 for egfr in calc.eGFRs:
2135 if egfr.numeric_value is None:
2136 continue
2137 tts.append(egfr.format (
2138 left_margin = 0,
2139 width = 50,
2140 eol = '\n',
2141 with_formula = False,
2142 with_warnings = True,
2143 with_variables = False,
2144 with_sub_results = False,
2145 return_list = False
2146 ))
2147 val.SetToolTip('\n'.join(tts))
2148 szr = wx.BoxSizer(wx.HORIZONTAL)
2149 szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3)
2150 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL)
2151 self._GSZR_lab.Add(szr)
2152
2153
2154 for pk_result in most_recent_results:
2155 result = most_recent_results[pk_result]
2156
2157 lbl = wx.StaticText(self, -1, '%s:' % result['unified_abbrev'])
2158 lbl.SetForegroundColour('blue')
2159
2160 indicate_attention = False
2161 if result.is_considered_abnormal:
2162 indicate_attention = True
2163
2164 max_age = None
2165 try:
2166 max_age = loinc_max_age[result['loinc_tt']]
2167 max_age_str = loinc_max_age_str[result['loinc_tt']]
2168 except KeyError:
2169 try:
2170 max_age = loinc_max_age[result['loinc_meta']]
2171 max_age_str = loinc_max_age_str[result['loinc_meta']]
2172 except KeyError:
2173 pass
2174 subst2monitor = None
2175 try:
2176 subst2monitor = loincs2monitor_data[result['loinc_tt']]['substance']
2177 except KeyError:
2178 try:
2179 subst2monitor = loincs2monitor_data[result['loinc_meta']]['substance']
2180 except KeyError:
2181 pass
2182 monitor_comment = None
2183 try:
2184 monitor_comment = loincs2monitor_data[result['loinc_tt']]['comment']
2185 except KeyError:
2186 try:
2187 monitor_comment = loincs2monitor_data[result['loinc_meta']]['comment']
2188 except KeyError:
2189 pass
2190 result_age = now - result['clin_when']
2191 unhappy_reasons = []
2192 if result.is_considered_abnormal:
2193 indicator = result.formatted_abnormality_indicator
2194 if indicator == '':
2195 unhappy_reasons.append(_(' - abnormal'))
2196 else:
2197 unhappy_reasons.append(_(' - abnormal: %s') % indicator)
2198 if max_age is not None:
2199 if result_age.total_seconds() > max_age:
2200 unhappy_reasons.append(_(' - too old: %s ago (max: %s)') % (
2201 gmDateTime.format_interval_medically(result_age),
2202 max_age_str
2203 ))
2204
2205 tt = [_('Most recent: %s ago') % gmDateTime.format_interval_medically(result_age)]
2206 if subst2monitor is not None:
2207 tt.append(_('Why monitor: %s') % subst2monitor)
2208 if monitor_comment is not None:
2209 tt.append(' %s' % monitor_comment)
2210 if len(unhappy_reasons) > 0:
2211 indicate_attention = True
2212 tt.append(_('Problems:'))
2213 tt.extend(unhappy_reasons)
2214 tt = '%s\n\n%s' % (
2215 '\n'.join(tt),
2216 result.format()
2217 )
2218
2219 val = wx.StaticText(self, -1, '%s%s%s' % (
2220 result['unified_val'],
2221 gmTools.coalesce(result['val_unit'], '', ' %s'),
2222 gmTools.bool2subst(indicate_attention, gmTools.u_frowning_face, '', '')
2223 ))
2224 val.SetToolTip(tt)
2225 if result.is_considered_abnormal:
2226 val.SetForegroundColour('red')
2227 szr = wx.BoxSizer(wx.HORIZONTAL)
2228 szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3)
2229 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL)
2230 self._GSZR_lab.Add(szr)
2231
2232
2233
2234 for loinc in loincs2monitor_missing:
2235
2236 loinc_data = gmLOINC.loinc2data(loinc)
2237 if loinc_data is None:
2238 loinc_str = loinc
2239 else:
2240 loinc_str = loinc_data['term']
2241 val = wx.StaticText(self, -1, '%s!' % loinc_str)
2242 tt = [
2243 _('No test result for: %s (%s)') % (loinc_str, loinc),
2244 '',
2245 _('Why monitor: %s' % loincs2monitor_data[loinc]['substance'])
2246 ]
2247 try:
2248 tt.append(' %s' % loincs2monitor_data[loinc]['comment'])
2249 except KeyError:
2250 pass
2251 val.SetToolTip('\n'.join(tt))
2252 val.SetForegroundColour('orange')
2253 szr = wx.BoxSizer(wx.HORIZONTAL)
2254 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL)
2255 self._GSZR_lab.Add(szr)
2256
2257 self._HLINE_lab.Show()
2258 self.Layout()
2259
2260
2262 gfrs = patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, max_no_of_results = 1)
2263 if len(gfrs) == 0:
2264 calc = gmClinicalCalculator.cClinicalCalculator()
2265 calc.patient = patient
2266 gfr = calc.eGFR
2267 if gfr.numeric_value is None:
2268 msg = _('GFR: ?')
2269 tt = gfr.message
2270 else:
2271 msg = _('eGFR: %.1f (%s)') % (
2272 gfr.numeric_value,
2273 gmDateTime.pydt_strftime (
2274 gfr.date_valid,
2275 format = '%b %Y'
2276 )
2277 )
2278 egfrs = calc.eGFRs
2279 tts = []
2280 for egfr in egfrs:
2281 if egfr.numeric_value is None:
2282 continue
2283 tts.append(egfr.format (
2284 left_margin = 0,
2285 width = 50,
2286 eol = '\n',
2287 with_formula = False,
2288 with_warnings = True,
2289 with_variables = False,
2290 with_sub_results = False,
2291 return_list = False
2292 ))
2293 tt = '\n'.join(tts)
2294 else:
2295 gfr = gfrs[0]
2296 msg = '%s: %s %s (%s)\n' % (
2297 gfr['unified_abbrev'],
2298 gfr['unified_val'],
2299 gmTools.coalesce(gfr['abnormality_indicator'], '', ' (%s)'),
2300 gmDateTime.pydt_strftime (
2301 gfr['clin_when'],
2302 format = '%b %Y'
2303 )
2304 )
2305 tt = _('GFR reported by path lab')
2306
2307 self._LBL_gfr.SetLabel(msg)
2308 self._LBL_gfr.SetToolTip(tt)
2309 self._LBL_gfr.Refresh()
2310 self.Layout()
2311
2312
2314 self._LBL_gfr.SetLabel(_('GFR: ?'))
2315 self._LBL_gfr.Refresh()
2316 self.Layout()
2317
2318
2319
2320
2322 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
2323 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
2324 gmDispatcher.connect(signal = 'clin.substance_intake_mod_db', receiver = self._schedule_data_reget)
2325 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._on_test_result_mod)
2326
2327
2330
2331
2345
2347 self._schedule_data_reget()
2348
2351
2354
2357
2360
2363
2365 event.Skip()
2366 selected_item_idx = self._CHCE_grouping.GetSelection()
2367 if selected_item_idx is wx.NOT_FOUND:
2368 return
2369 self._grid_substances.grouping_mode = self._CHCE_grouping.GetClientData(selected_item_idx)
2370
2373
2376
2379
2382
2385
2388
2391
2394
2395
2396
2397
2398 if __name__ == '__main__':
2399
2400 if len(sys.argv) < 2:
2401 sys.exit()
2402
2403 if sys.argv[1] != 'test':
2404 sys.exit()
2405
2406 from Gnumed.business import gmPersonSearch
2407
2408 pat = gmPersonSearch.ask_for_patient()
2409 if pat is None:
2410 sys.exit()
2411 gmPerson.set_active_patient(patient = pat)
2412
2413
2414 app = wx.PyWidgetTester(size = (600, 300))
2415 app.SetWidget(cSubstanceIntakeObjectPhraseWheel, -1)
2416 app.MainLoop()
2417
2418