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:
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:
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
1390 rows = {}
1391
1392 for row, col in self.get_selected_cells():
1393 rows[row] = True
1394
1395 return rows.keys()
1396
1397
1400
1401
1403 return self.__row_data.values()
1404
1405
1407
1408 self.empty_grid()
1409
1410 if self.__patient is None:
1411 return
1412
1413 emr = self.__patient.emr
1414 meds = emr.get_current_medications (
1415 order_by = self.__grouping2order_by_clauses[self.__grouping_mode],
1416 include_unapproved = self.__filter_show_unapproved,
1417 include_inactive = self.__filter_show_inactive
1418 )
1419 if not meds:
1420 return
1421
1422 self.BeginBatch()
1423
1424
1425 labels = self.__grouping2col_labels[self.__grouping_mode]
1426 if self.__filter_show_unapproved:
1427 self.AppendCols(numCols = len(labels) + 1)
1428 else:
1429 self.AppendCols(numCols = len(labels))
1430 for col_idx in range(len(labels)):
1431 self.SetColLabelValue(col_idx, labels[col_idx])
1432 if self.__filter_show_unapproved:
1433
1434 self.SetColLabelValue(len(labels), '')
1435 self.SetColSize(len(labels), 40)
1436
1437 self.AppendRows(numRows = len(meds))
1438
1439
1440 for row_idx in range(len(meds)):
1441 med = meds[row_idx]
1442 self.__row_data[row_idx] = med
1443
1444 if med['is_currently_active'] is True:
1445 atcs = []
1446 if med['atc_substance'] is not None:
1447 atcs.append(med['atc_substance'])
1448 allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (med['substance'],))
1449 if allg not in [None, False]:
1450 attr = self.GetOrCreateCellAttr(row_idx, 0)
1451 if allg['type'] == 'allergy':
1452 attr.SetTextColour('red')
1453 else:
1454
1455
1456
1457 attr.SetTextColour('magenta')
1458 self.SetRowAttr(row_idx, attr)
1459 else:
1460 attr = self.GetOrCreateCellAttr(row_idx, 0)
1461 attr.SetTextColour('grey')
1462 self.SetRowAttr(row_idx, attr)
1463
1464 if self.__grouping_mode in ['episode', 'start']:
1465 if med['pk_episode'] is None:
1466 self.__prev_cell_0 = None
1467 epi = gmTools.u_diameter
1468 else:
1469 if self.__prev_cell_0 == med['episode']:
1470 epi = ''
1471 else:
1472 self.__prev_cell_0 = med['episode']
1473 epi = gmTools.coalesce(med['episode'], '')
1474 self.SetCellValue(row_idx, 0, gmTools.wrap(text = epi, width = 40))
1475
1476 self.SetCellValue(row_idx, 1, med['substance'])
1477 self.SetCellValue(row_idx, 2, '%s %s' % (med['amount'], med['unit']))
1478 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], ''))
1479 self.SetCellValue(row_idx, 4, med.medically_formatted_start_end)
1480
1481 if med['pk_drug_product'] is None:
1482 product = '%s (%s)' % (gmTools.u_diameter, med['l10n_preparation'])
1483 else:
1484 if med['is_fake_product']:
1485 product = '%s (%s)' % (
1486 gmTools.coalesce(med['product'], '', _('%s <fake>')),
1487 med['l10n_preparation']
1488 )
1489 else:
1490 product = '%s (%s)' % (
1491 gmTools.coalesce(med['product'], ''),
1492 med['l10n_preparation']
1493 )
1494 self.SetCellValue(row_idx, 5, gmTools.wrap(text = product, width = 35))
1495
1496 elif self.__grouping_mode == 'issue':
1497 if med['pk_health_issue'] is None:
1498 self.__prev_cell_0 = None
1499 issue = '%s%s' % (
1500 gmTools.u_diameter,
1501 gmTools.coalesce(med['episode'], '', ' (%s)')
1502 )
1503 else:
1504 if self.__prev_cell_0 == med['health_issue']:
1505 issue = ''
1506 else:
1507 self.__prev_cell_0 = med['health_issue']
1508 issue = med['health_issue']
1509 self.SetCellValue(row_idx, 0, gmTools.wrap(text = issue, width = 40))
1510
1511 self.SetCellValue(row_idx, 1, med['substance'])
1512 self.SetCellValue(row_idx, 2, '%s %s' % (med['amount'], med['unit']))
1513 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], ''))
1514 self.SetCellValue(row_idx, 4, med.medically_formatted_start_end)
1515
1516 if med['pk_drug_product'] is None:
1517 product = '%s (%s)' % (gmTools.u_diameter, med['l10n_preparation'])
1518 else:
1519 if med['is_fake_product']:
1520 product = '%s (%s)' % (
1521 gmTools.coalesce(med['product'], '', _('%s <fake>')),
1522 med['l10n_preparation']
1523 )
1524 else:
1525 product = '%s (%s)' % (
1526 gmTools.coalesce(med['product'], ''),
1527 med['l10n_preparation']
1528 )
1529 self.SetCellValue(row_idx, 5, gmTools.wrap(text = product, width = 35))
1530
1531 elif self.__grouping_mode == 'product':
1532
1533 if med['pk_drug_product'] is None:
1534 self.__prev_cell_0 = None
1535 product = '%s (%s)' % (
1536 gmTools.u_diameter,
1537 med['l10n_preparation']
1538 )
1539 else:
1540 if self.__prev_cell_0 == med['product']:
1541 product = ''
1542 else:
1543 self.__prev_cell_0 = med['product']
1544 if med['is_fake_product']:
1545 product = '%s (%s)' % (
1546 gmTools.coalesce(med['product'], '', _('%s <fake>')),
1547 med['l10n_preparation']
1548 )
1549 else:
1550 product = '%s (%s)' % (
1551 gmTools.coalesce(med['product'], ''),
1552 med['l10n_preparation']
1553 )
1554 self.SetCellValue(row_idx, 0, gmTools.wrap(text = product, width = 35))
1555
1556 self.SetCellValue(row_idx, 1, gmTools.coalesce(med['schedule'], ''))
1557 self.SetCellValue(row_idx, 2, med['substance'])
1558 self.SetCellValue(row_idx, 3, '%s %s' % (med['amount'], med['unit']))
1559 self.SetCellValue(row_idx, 4, med.medically_formatted_start_end)
1560
1561 if med['pk_health_issue'] is None:
1562 issue = '%s%s' % (
1563 gmTools.u_diameter,
1564 gmTools.coalesce(med['episode'], '', ' (%s)')
1565 )
1566 else:
1567 issue = gmTools.coalesce(med['health_issue'], '')
1568 self.SetCellValue(row_idx, 5, gmTools.wrap(text = issue, width = 40))
1569
1570 else:
1571 raise ValueError('unknown grouping mode [%s]' % self.__grouping_mode)
1572
1573 if med['notes'] is not None:
1574 self.SetCellValue(row_idx, 6, gmTools.wrap(text = med['notes'], width = 50))
1575
1576 if self.__filter_show_unapproved:
1577 self.SetCellValue (
1578 row_idx,
1579 len(labels),
1580 gmTools.bool2subst(med['intake_is_approved_of'], gmTools.u_checkmark_thin, gmTools.u_frowning_face, '?')
1581 )
1582 font = self.GetCellFont(row_idx, len(labels))
1583 font.SetPointSize(font.GetPointSize() + 2)
1584 self.SetCellFont(row_idx, len(labels), font)
1585
1586
1587
1588 self.AutoSize()
1589 self.EndBatch()
1590
1592 self.BeginBatch()
1593 self.ClearGrid()
1594
1595
1596 if self.GetNumberRows() > 0:
1597 self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
1598 if self.GetNumberCols() > 0:
1599 self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
1600 self.EndBatch()
1601 self.__row_data = {}
1602 self.__prev_cell_0 = None
1603
1604
1606
1607 if len(self.__row_data) == 0:
1608 return
1609
1610 sel_rows = self.get_selected_rows()
1611 if len(sel_rows) != 1:
1612 return
1613
1614 drug_db = gmSubstanceMgmtWidgets.get_drug_database(patient = self.__patient)
1615 if drug_db is None:
1616 return
1617
1618 intake = self.get_selected_data()[0]
1619 if intake['product'] is None:
1620 drug_db.show_info_on_substance(substance_intake = intake)
1621 else:
1622 drug_db.show_info_on_drug(substance_intake = intake)
1623
1624
1632
1633
1636
1637
1647
1648
1654
1668
1671
1685
1686
1700
1701
1717
1721
1822
1823
1824
1825
1827 self.CreateGrid(0, 1)
1828 self.EnableEditing(0)
1829 self.EnableDragGridSize(1)
1830 self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows)
1831
1832 self.SetColLabelAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTER)
1833
1834 self.SetRowLabelSize(0)
1835 self.SetRowLabelAlignment(horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
1836
1837
1838
1839
1841 return self.__patient
1842
1846
1847 patient = property(_get_patient, _set_patient)
1848
1850 return self.__grouping_mode
1851
1855
1856 grouping_mode = property(_get_grouping_mode, _set_grouping_mode)
1857
1859 return self.__filter_show_unapproved
1860
1864
1865 filter_show_unapproved = property(_get_filter_show_unapproved, _set_filter_show_unapproved)
1866
1868 return self.__filter_show_inactive
1869
1873
1874 filter_show_inactive = property(_get_filter_show_inactive, _set_filter_show_inactive)
1875
1876
1877
1879
1880 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
1881
1882
1883
1884
1885 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
1886
1888 """Calculate where the mouse is and set the tooltip dynamically."""
1889
1890
1891
1892 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906 row, col = self.XYToCell(x, y)
1907
1908 if row == self.__prev_tooltip_row:
1909 return
1910
1911 self.__prev_tooltip_row = row
1912
1913 try:
1914 evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row))
1915 except KeyError:
1916 pass
1917
1922
1923
1942
1943
1955
1956 gmCfgWidgets.configure_string_option (
1957 message = _(
1958 'GNUmed will use this URL to access a website which lets\n'
1959 'you report an adverse drug reaction (ADR).\n'
1960 '\n'
1961 'If you leave this empty it will fall back\n'
1962 'to an URL for reporting ADRs in Germany.'
1963 ),
1964 option = 'external.urls.report_ADR',
1965 bias = 'user',
1966 default_value = gmMedication.URL_drug_adr_german_default,
1967 validator = is_valid
1968 )
1969
1970
1971 from Gnumed.wxGladeWidgets import wxgCurrentSubstancesPnl
1972
1973 -class cCurrentSubstancesPnl(wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl, gmRegetMixin.cRegetOnPaintMixin):
1974
1975 """Panel holding a grid with current substances. Used as notebook page."""
1976
1978
1979 wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl.__init__(self, *args, **kwargs)
1980 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1981
1982 self.__grouping_choice_labels = [
1983 {'label': _('Health issue'), 'data': 'issue'} ,
1984 {'label': _('Drug product'), 'data': 'product'},
1985 {'label': _('Episode'), 'data': 'episode'},
1986 {'label': _('Started'), 'data': 'start'}
1987 ]
1988 self.__lab_panel = None
1989
1990 self.__init_ui()
1991 self.__register_interests()
1992
1993
1995 self._CHCE_grouping.Clear()
1996 for option in self.__grouping_choice_labels:
1997 self._CHCE_grouping.Append(option['label'], option['data'])
1998 self._CHCE_grouping.SetSelection(0)
1999
2000 tt = self._BTN_heart.GetToolTipText()
2001 try:
2002 self._BTN_heart.SetToolTip(tt % gmMedication.URL_long_qt)
2003 except TypeError:
2004 _log.exception('translation error: %s', tt)
2005
2006 tt = self._BTN_kidneys.GetToolTipText()
2007 try:
2008 self._BTN_kidneys.SetToolTip(tt % gmMedication.URL_renal_insufficiency)
2009 except TypeError:
2010 _log.exception('translation error: %s', tt)
2011
2012
2013
2014
2016 """Populate cells with data from model."""
2017 pat = gmPerson.gmCurrentPatient()
2018 if pat.connected:
2019 self._grid_substances.patient = pat
2020 self.__refresh_gfr(pat)
2021 self.__refresh_lab(patient = pat)
2022 else:
2023 self._grid_substances.patient = None
2024 self.__clear_gfr()
2025 self.__refresh_lab(patient = None)
2026 return True
2027
2028
2030
2031 self._GSZR_lab.Clear(True)
2032 self._HLINE_lab.Hide()
2033
2034 if patient is None:
2035 self.Layout()
2036 return
2037
2038 emr = patient.emr
2039 most_recent_results = {}
2040
2041
2042 loincs2monitor = set()
2043 loincs2monitor_data = {}
2044 loinc_max_age = {}
2045 loinc_max_age_str = {}
2046 for intake in self._grid_substances.get_row_data():
2047 for l in intake['loincs']:
2048 loincs2monitor.add(l['loinc'])
2049 loincs2monitor_data[l['loinc']] = {
2050 'substance': intake['substance'],
2051 'comment': l['comment']
2052 }
2053 if l['max_age_in_secs'] is not None:
2054 try:
2055 if loinc_max_age[l['loinc']] > l['max_age_in_secs']:
2056 loinc_max_age[l['loinc']] = l['max_age_in_secs']
2057 loinc_max_age_str[l['loinc']] = l['max_age_str']
2058 except KeyError:
2059 loinc_max_age[l['loinc']] = l['max_age_in_secs']
2060 loinc_max_age_str[l['loinc']] = l['max_age_str']
2061 loincs2monitor_missing = loincs2monitor.copy()
2062 for loinc in loincs2monitor:
2063 results = emr.get_most_recent_results_in_loinc_group(loincs = [loinc], max_no_of_results = 1)
2064 if len(results) == 0:
2065 continue
2066 loincs2monitor_missing.remove(loinc)
2067
2068 result = results[0]
2069 most_recent_results[result['pk_test_result']] = result
2070
2071
2072 if self.__lab_panel is not None:
2073 for result in self.__lab_panel.get_most_recent_results (
2074 pk_patient = patient.ID,
2075 order_by = 'unified_abbrev',
2076 group_by_meta_type = True
2077 ):
2078 try: loincs2monitor_missing.remove(result['loinc_tt'])
2079 except KeyError: pass
2080 try: loincs2monitor_missing.remove(result['loinc_meta'])
2081 except KeyError: pass
2082
2083 most_recent_results[result['pk_test_result']] = result
2084
2085
2086 gfrs = emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, max_no_of_results = 1)
2087 gfr = gfrs[0] if len(gfrs) > 0 else None
2088 creas = emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
2089 crea = creas[0] if len(creas) > 0 else None
2090 edc = emr.EDC
2091
2092
2093 if edc is not None:
2094 if emr.EDC_is_fishy:
2095 lbl = wx.StaticText(self, -1, _('EDC (!?!):'))
2096 val = wx.StaticText(self, -1, gmDateTime.pydt_strftime(edc, format = '%Y %b %d'))
2097 else:
2098 lbl = wx.StaticText(self, -1, _('EDC:'))
2099 val = wx.StaticText(self, -1, gmDateTime.pydt_strftime(edc, format = '%Y %b %d'))
2100 lbl.SetForegroundColour('blue')
2101 szr = wx.BoxSizer(wx.HORIZONTAL)
2102 szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3)
2103 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL)
2104 self._GSZR_lab.Add(szr)
2105
2106
2107 if crea is None:
2108 gfr_3_months_older_than_crea = False
2109 if gfr is not None:
2110 most_recent_results = [gfr] + most_recent_results
2111 elif gfr is None:
2112 gfr_3_months_older_than_crea = True
2113 else:
2114 three_months = pydt.timedelta(weeks = 14)
2115 gfr_3_months_older_than_crea = (crea['clin_when'] - gfr['clin_when']) > three_months
2116 if not gfr_3_months_older_than_crea:
2117 most_recent_results = [gfr] + most_recent_results
2118
2119
2120 now = gmDateTime.pydt_now_here()
2121 if gfr_3_months_older_than_crea:
2122 calc = gmClinicalCalculator.cClinicalCalculator()
2123 calc.patient = patient
2124 gfr = calc.eGFR
2125 if gfr.numeric_value is None:
2126 gfr_msg = '?'
2127 else:
2128 gfr_msg = _('%.1f (%s ago)') % (
2129 gfr.numeric_value,
2130 gmDateTime.format_interval_medically(now - gfr.date_valid)
2131 )
2132 lbl = wx.StaticText(self, -1, _('eGFR:'))
2133 lbl.SetForegroundColour('blue')
2134 val = wx.StaticText(self, -1, gfr_msg)
2135 tts = []
2136 for egfr in calc.eGFRs:
2137 if egfr.numeric_value is None:
2138 continue
2139 tts.append(egfr.format (
2140 left_margin = 0,
2141 width = 50,
2142 eol = '\n',
2143 with_formula = False,
2144 with_warnings = True,
2145 with_variables = False,
2146 with_sub_results = False,
2147 return_list = False
2148 ))
2149 val.SetToolTip('\n'.join(tts))
2150 szr = wx.BoxSizer(wx.HORIZONTAL)
2151 szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3)
2152 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL)
2153 self._GSZR_lab.Add(szr)
2154
2155
2156 for pk_result in most_recent_results:
2157 result = most_recent_results[pk_result]
2158
2159 lbl = wx.StaticText(self, -1, '%s:' % result['unified_abbrev'])
2160 lbl.SetForegroundColour('blue')
2161
2162 indicate_attention = False
2163 if result.is_considered_abnormal:
2164 indicate_attention = True
2165
2166 max_age = None
2167 try:
2168 max_age = loinc_max_age[result['loinc_tt']]
2169 max_age_str = loinc_max_age_str[result['loinc_tt']]
2170 except KeyError:
2171 try:
2172 max_age = loinc_max_age[result['loinc_meta']]
2173 max_age_str = loinc_max_age_str[result['loinc_meta']]
2174 except KeyError:
2175 pass
2176 subst2monitor = None
2177 try:
2178 subst2monitor = loincs2monitor_data[result['loinc_tt']]['substance']
2179 except KeyError:
2180 try:
2181 subst2monitor = loincs2monitor_data[result['loinc_meta']]['substance']
2182 except KeyError:
2183 pass
2184 monitor_comment = None
2185 try:
2186 monitor_comment = loincs2monitor_data[result['loinc_tt']]['comment']
2187 except KeyError:
2188 try:
2189 monitor_comment = loincs2monitor_data[result['loinc_meta']]['comment']
2190 except KeyError:
2191 pass
2192 result_age = now - result['clin_when']
2193 unhappy_reasons = []
2194 if result.is_considered_abnormal:
2195 indicator = result.formatted_abnormality_indicator
2196 if indicator == '':
2197 unhappy_reasons.append(_(' - abnormal'))
2198 else:
2199 unhappy_reasons.append(_(' - abnormal: %s') % indicator)
2200 if max_age is not None:
2201 if result_age.total_seconds() > max_age:
2202 unhappy_reasons.append(_(' - too old: %s ago (max: %s)') % (
2203 gmDateTime.format_interval_medically(result_age),
2204 max_age_str
2205 ))
2206
2207 tt = [_('Most recent: %s ago') % gmDateTime.format_interval_medically(result_age)]
2208 if subst2monitor is not None:
2209 tt.append(_('Why monitor: %s') % subst2monitor)
2210 if monitor_comment is not None:
2211 tt.append(' %s' % monitor_comment)
2212 if len(unhappy_reasons) > 0:
2213 indicate_attention = True
2214 tt.append(_('Problems:'))
2215 tt.extend(unhappy_reasons)
2216 tt = '%s\n\n%s' % (
2217 '\n'.join(tt),
2218 result.format()
2219 )
2220
2221 val = wx.StaticText(self, -1, '%s%s%s' % (
2222 result['unified_val'],
2223 gmTools.coalesce(result['val_unit'], '', ' %s'),
2224 gmTools.bool2subst(indicate_attention, gmTools.u_frowning_face, '', '')
2225 ))
2226 val.SetToolTip(tt)
2227 if result.is_considered_abnormal:
2228 val.SetForegroundColour('red')
2229 szr = wx.BoxSizer(wx.HORIZONTAL)
2230 szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3)
2231 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL)
2232 self._GSZR_lab.Add(szr)
2233
2234
2235
2236 for loinc in loincs2monitor_missing:
2237
2238 loinc_data = gmLOINC.loinc2data(loinc)
2239 if loinc_data is None:
2240 loinc_str = loinc
2241 else:
2242 loinc_str = loinc_data['term']
2243 val = wx.StaticText(self, -1, '%s!' % loinc_str)
2244 tt = [
2245 _('No test result for: %s (%s)') % (loinc_str, loinc),
2246 '',
2247 _('Why monitor: %s' % loincs2monitor_data[loinc]['substance'])
2248 ]
2249 try:
2250 tt.append(' %s' % loincs2monitor_data[loinc]['comment'])
2251 except KeyError:
2252 pass
2253 val.SetToolTip('\n'.join(tt))
2254 val.SetForegroundColour('orange')
2255 szr = wx.BoxSizer(wx.HORIZONTAL)
2256 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL)
2257 self._GSZR_lab.Add(szr)
2258
2259 self._HLINE_lab.Show()
2260 self.Layout()
2261
2262
2264 gfrs = patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, max_no_of_results = 1)
2265 if len(gfrs) == 0:
2266 calc = gmClinicalCalculator.cClinicalCalculator()
2267 calc.patient = patient
2268 gfr = calc.eGFR
2269 if gfr.numeric_value is None:
2270 msg = _('GFR: ?')
2271 tt = gfr.message
2272 else:
2273 msg = _('eGFR: %.1f (%s)') % (
2274 gfr.numeric_value,
2275 gmDateTime.pydt_strftime (
2276 gfr.date_valid,
2277 format = '%b %Y'
2278 )
2279 )
2280 egfrs = calc.eGFRs
2281 tts = []
2282 for egfr in egfrs:
2283 if egfr.numeric_value is None:
2284 continue
2285 tts.append(egfr.format (
2286 left_margin = 0,
2287 width = 50,
2288 eol = '\n',
2289 with_formula = False,
2290 with_warnings = True,
2291 with_variables = False,
2292 with_sub_results = False,
2293 return_list = False
2294 ))
2295 tt = '\n'.join(tts)
2296 else:
2297 gfr = gfrs[0]
2298 msg = '%s: %s %s (%s)\n' % (
2299 gfr['unified_abbrev'],
2300 gfr['unified_val'],
2301 gmTools.coalesce(gfr['abnormality_indicator'], '', ' (%s)'),
2302 gmDateTime.pydt_strftime (
2303 gfr['clin_when'],
2304 format = '%b %Y'
2305 )
2306 )
2307 tt = _('GFR reported by path lab')
2308
2309 self._LBL_gfr.SetLabel(msg)
2310 self._LBL_gfr.SetToolTip(tt)
2311 self._LBL_gfr.Refresh()
2312 self.Layout()
2313
2314
2316 self._LBL_gfr.SetLabel(_('GFR: ?'))
2317 self._LBL_gfr.Refresh()
2318 self.Layout()
2319
2320
2321
2322
2324 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
2325 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
2326 gmDispatcher.connect(signal = 'clin.substance_intake_mod_db', receiver = self._schedule_data_reget)
2327 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._on_test_result_mod)
2328
2329
2332
2333
2347
2349 self._schedule_data_reget()
2350
2353
2356
2359
2362
2365
2367 event.Skip()
2368 selected_item_idx = self._CHCE_grouping.GetSelection()
2369 if selected_item_idx is wx.NOT_FOUND:
2370 return
2371 self._grid_substances.grouping_mode = self._CHCE_grouping.GetClientData(selected_item_idx)
2372
2375
2378
2381
2384
2387
2390
2393
2396
2397
2398
2399
2400 if __name__ == '__main__':
2401
2402 if len(sys.argv) < 2:
2403 sys.exit()
2404
2405 if sys.argv[1] != 'test':
2406 sys.exit()
2407
2408 from Gnumed.business import gmPersonSearch
2409
2410 pat = gmPersonSearch.ask_for_patient()
2411 if pat is None:
2412 sys.exit()
2413 gmPerson.set_active_patient(patient = pat)
2414
2415
2416 app = wx.PyWidgetTester(size = (600, 300))
2417 app.SetWidget(cSubstanceIntakeObjectPhraseWheel, -1)
2418 app.MainLoop()
2419
2420