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 gfr = emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, no_of_results = 1)
408 if gfr is None:
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 msg += '%s: %s %s (%s)\n' % (
433 gfr['unified_abbrev'],
434 gfr['unified_val'],
435 gmTools.coalesce(gfr['abnormality_indicator'], '', ' (%s)'),
436 gmDateTime.pydt_strftime (
437 gfr['clin_when'],
438 format = '%Y %b %d'
439 )
440 )
441 tooltip += _('GFR reported by path lab')
442
443
444 edc = emr.EDC
445 if edc is not None:
446 msg += '\n\n'
447 if emr.EDC_is_fishy:
448 msg += _('EDC (!?!): %s') % gmDateTime.pydt_strftime(edc, format = '%Y %b %d')
449 tooltip += _(
450 'The Expected Date of Confinement is rather questionable.\n'
451 '\n'
452 'Please check patient age, patient gender, time until/since EDC.'
453 )
454 else:
455 msg += _('EDC: %s') % gmDateTime.pydt_strftime(edc, format = '%Y %b %d')
456
457 self._LBL_allergies.SetLabel(msg)
458 self._LBL_allergies.SetToolTip(tooltip)
459
460
462
463 drug = self._PRW_drug.GetData(as_instance = True)
464 if drug is None:
465 self._LBL_drug_details.SetLabel('')
466 self._LBL_drug_details.SetToolTip('')
467 self.Layout()
468 return
469
470 if len(drug['components']) == 0:
471 comps = _('<no components>')
472 else:
473 comps = '\n'.join ([
474 ' %s %s%s%s' % (
475 c['substance'],
476 c['amount'],
477 c['unit'],
478 gmTools.coalesce(c['dose_unit'], '', '/%s')
479 )
480 for c in drug['components']
481 ])
482 self._LBL_drug_details.SetLabel('%s\n%s' % (drug['product'], comps))
483 self._LBL_drug_details.SetToolTip(drug.format())
484 self.Layout()
485 return
486
487
488
489
491
492 self._PRW_drug.display_as_valid(True)
493
494
495 if self.mode == 'edit':
496 return True
497
498 selected_drug = self._PRW_drug.GetData(as_instance = True)
499
500
501 if selected_drug is None:
502 val = self._PRW_drug.GetValue().strip()
503 if val == '':
504 self._PRW_drug.display_as_valid(False)
505 self._PRW_drug.SetFocus()
506 return False
507
508 drug = gmSubstanceMgmtWidgets.edit_single_component_generic_drug (
509 parent = self,
510 drug = None,
511 single_entry = True,
512 fields = {'substance': {'value': val, 'data': None}},
513 return_drug = True
514 )
515 if drug is None:
516 self._PRW_drug.display_as_valid(False)
517 self._PRW_drug.SetFocus()
518 return False
519 comp = drug.components[0]
520 self._PRW_drug.SetText (
521 _('%s w/ %s%s%s of %s') % (
522 comp['product'],
523 comp['amount'],
524 comp['unit'],
525 gmTools.coalesce(comp['dose_unit'], '', '/%s'),
526 comp['substance']
527 ),
528 drug['pk_drug_product']
529 )
530 selected_drug = drug
531 self.__refresh_drug_details()
532 self._PRW_drug.display_as_valid(True)
533 self._PRW_drug.SetFocus()
534
535
536
537 return False
538
539
540 if selected_drug.exists_as_intake(pk_patient = gmPerson.gmCurrentPatient().ID):
541 title = _('Adding substance intake entry')
542 msg = _(
543 'The patient is already taking\n'
544 '\n'
545 ' %s\n'
546 '\n'
547 'You will want to adjust the schedule\n'
548 'rather than document the intake twice.'
549 ) % self._PRW_drug.GetValue().strip()
550 gmGuiHelpers.gm_show_warning(aTitle = title, aMessage = msg)
551 self._PRW_drug.display_as_valid(False)
552 self._PRW_drug.SetFocus()
553 return False
554
555 self._PRW_drug.display_as_valid(True)
556 return True
557
558
560
561 validity = self._check_drug_is_valid()
562
563
564 if self._CHBOX_approved.IsChecked():
565 if self._PRW_episode.GetValue().strip() == '':
566 self._PRW_episode.display_as_valid(False)
567 validity = False
568 else:
569 self._PRW_episode.display_as_valid(True)
570
571 if self._PRW_duration.GetValue().strip() in ['', gmTools.u_infinity]:
572 self._PRW_duration.display_as_valid(True)
573 else:
574 if self._PRW_duration.GetData() is None:
575
576 if gmDateTime.str2interval(self._PRW_duration.GetValue()) is None:
577 self._PRW_duration.display_as_valid(False)
578 validity = False
579
580 else:
581 self._PRW_duration.display_as_valid(True)
582
583 else:
584 self._PRW_duration.display_as_valid(True)
585
586
587 started = None
588 if self._CHBOX_start_unknown.IsChecked() is False:
589 started = self._DP_started.GetData()
590 if started is None:
591 self._DP_started.display_as_valid(False)
592 self._DP_started.SetFocus()
593 validity = False
594 else:
595 self._DP_started.display_as_valid(True)
596
597 if validity is False:
598 self.StatusText = _('Input incomplete/invalid for saving as substance intake.')
599
600
601 discontinued = self._DP_discontinued.GetData()
602 if discontinued is not None:
603 now = gmDateTime.pydt_now_here().replace (
604 hour = 23,
605 minute = 59,
606 second = 59,
607 microsecond = 111111
608 )
609
610 if discontinued > now:
611 self._DP_discontinued.display_as_valid(False)
612 validity = False
613 self.StatusText = _('Discontinued (%s) in the future (now: %s)!') % (discontinued, now)
614 else:
615 if started is not None:
616 started = started.replace (
617 hour = 0,
618 minute = 0,
619 second = 0,
620 microsecond = 1
621 )
622
623 if started > discontinued:
624 self._DP_started.display_as_valid(False)
625 self._DP_discontinued.display_as_valid(False)
626 validity = False
627 self.StatusText = _('Discontinued (%s) before started (%s) !') % (discontinued, started)
628 else:
629 self._DP_started.display_as_valid(True)
630 self._DP_discontinued.display_as_valid(True)
631
632 return validity
633
634
636
637 epi = self._PRW_episode.GetData()
638 if epi is None:
639
640 epi = self._PRW_episode.GetData(can_create = True, is_open = True)
641
642 selected_drug = self._PRW_drug.GetData(as_instance = True)
643 intake = selected_drug.turn_into_intake (
644 encounter = gmPerson.gmCurrentPatient().emr.current_encounter['pk_encounter'],
645 episode = epi
646 )
647
648 if intake is None:
649 self.StatusText = _('Cannot add duplicate of (maybe inactive) substance intake.')
650 return False
651
652 intake['started'] = self._DP_started.GetData()
653 if self._CHBOX_start_unknown.IsChecked():
654 intake['comment_on_start'] = '?'
655 else:
656 intake['comment_on_start'] = self._PRW_start_certainty.GetValue().strip()
657 intake['discontinued'] = self._DP_discontinued.GetData()
658 if intake['discontinued'] is None:
659 intake['discontinue_reason'] = None
660 else:
661 intake['discontinue_reason'] = self._PRW_discontinue_reason.GetValue().strip()
662 intake['schedule'] = self._PRW_schedule.GetValue().strip()
663 intake['aim'] = self._PRW_aim.GetValue().strip()
664 intake['notes'] = self._PRW_notes.GetValue().strip()
665 intake['is_long_term'] = self._CHBOX_long_term.IsChecked()
666 intake['intake_is_approved_of'] = self._CHBOX_approved.IsChecked()
667 if self._PRW_duration.GetValue().strip() in ['', gmTools.u_infinity]:
668 intake['duration'] = None
669 else:
670 if self._PRW_duration.GetData() is None:
671 intake['duration'] = gmDateTime.str2interval(self._PRW_duration.GetValue())
672 else:
673 intake['duration'] = self._PRW_duration.GetData()
674 intake.save()
675
676 self.data = intake
677
678 return True
679
680
682
683
684 self.data['started'] = self._DP_started.GetData()
685 if self._CHBOX_start_unknown.IsChecked():
686 self.data['comment_on_start'] = '?'
687 else:
688 self.data['comment_on_start'] = self._PRW_start_certainty.GetValue().strip()
689 self.data['discontinued'] = self._DP_discontinued.GetData()
690 if self.data['discontinued'] is None:
691 self.data['discontinue_reason'] = None
692 else:
693 self.data['discontinue_reason'] = self._PRW_discontinue_reason.GetValue().strip()
694 self.data['schedule'] = self._PRW_schedule.GetValue()
695 self.data['is_long_term'] = self._CHBOX_long_term.IsChecked()
696 self.data['intake_is_approved_of'] = self._CHBOX_approved.IsChecked()
697 if self._PRW_duration.GetValue().strip() in ['', gmTools.u_infinity]:
698 self.data['duration'] = None
699 else:
700 if self._PRW_duration.GetData() is None:
701 self.data['duration'] = gmDateTime.str2interval(self._PRW_duration.GetValue())
702 else:
703 self.data['duration'] = self._PRW_duration.GetData()
704
705
706 self.data['aim'] = self._PRW_aim.GetValue()
707 self.data['notes'] = self._PRW_notes.GetValue()
708 epi = self._PRW_episode.GetData()
709 if epi is None:
710
711 epi = self._PRW_episode.GetData(can_create = True, is_open = True)
712 self.data['pk_episode'] = epi
713
714 self.data.save()
715
716 return True
717
718
720 self._PRW_drug.SetText('', None)
721
722 self._PRW_schedule.SetText('', None)
723 self._PRW_duration.SetText('', None)
724 self._PRW_aim.SetText('', None)
725 self._PRW_notes.SetText('', None)
726 self._PRW_episode.SetText('', None)
727
728 self._CHBOX_long_term.SetValue(False)
729 self._CHBOX_approved.SetValue(True)
730
731 self._CHBOX_start_unknown.SetValue(False)
732 self._DP_started.SetData(gmDateTime.pydt_now_here())
733 self._DP_started.Enable(True)
734 self._PRW_start_certainty.SetText('', None)
735 self._PRW_start_certainty.Enable(True)
736 self._DP_discontinued.SetData(None)
737 self._PRW_discontinue_reason.SetValue('')
738 self._PRW_discontinue_reason.Enable(False)
739
740 self.__refresh_drug_details()
741 self.__refresh_precautions()
742
743 self._PRW_drug.SetFocus()
744
745
747
748 self._PRW_drug.SetText (
749 _('%s w/ %s%s%s of %s') % (
750 self.data['product'],
751 self.data['amount'],
752 self.data['unit'],
753 gmTools.coalesce(self.data['dose_unit'], '', '/%s'),
754 self.data['substance']
755 ),
756 self.data['pk_drug_product']
757 )
758
759 self._PRW_drug.Disable()
760
761 if self.data['is_long_term']:
762 self._CHBOX_long_term.SetValue(True)
763 self._PRW_duration.Enable(False)
764 self._PRW_duration.SetText(gmTools.u_infinity, None)
765 self._BTN_discontinued_as_planned.Enable(False)
766 else:
767 self._CHBOX_long_term.SetValue(False)
768 self._PRW_duration.Enable(True)
769 self._BTN_discontinued_as_planned.Enable(True)
770 self._PRW_duration.SetData(self.data['duration'])
771 self._PRW_aim.SetText(gmTools.coalesce(self.data['aim'], ''), self.data['aim'])
772 self._PRW_notes.SetText(gmTools.coalesce(self.data['notes'], ''), self.data['notes'])
773 self._PRW_episode.SetData(self.data['pk_episode'])
774 self._PRW_schedule.SetText(gmTools.coalesce(self.data['schedule'], ''), self.data['schedule'])
775
776 self._CHBOX_approved.SetValue(self.data['intake_is_approved_of'])
777
778 self._DP_started.SetData(self.data['started'])
779 self._PRW_start_certainty.SetText(self.data['comment_on_start'], None)
780 if self.data['start_is_unknown']:
781 self._CHBOX_start_unknown.SetValue(True)
782 self._DP_started.Enable(False)
783 self._PRW_start_certainty.Enable(False)
784 else:
785 self._CHBOX_start_unknown.SetValue(False)
786 self._DP_started.Enable(True)
787 self._PRW_start_certainty.Enable(True)
788
789 self._DP_discontinued.SetData(self.data['discontinued'])
790 self._PRW_discontinue_reason.SetValue(gmTools.coalesce(self.data['discontinue_reason'], ''))
791 if self.data['discontinued'] is not None:
792 self._PRW_discontinue_reason.Enable()
793
794 self.__refresh_drug_details()
795 self.__refresh_precautions()
796
797 self._PRW_schedule.SetFocus()
798
799
801 self._refresh_as_new()
802
803 self._PRW_episode.SetData(self.data['pk_episode'])
804 self._DP_started.SetData(self.data['started'])
805
806 self._PRW_drug.SetFocus()
807
808
809
810
812 self.__refresh_drug_details()
813
814
816 drug = self._PRW_drug.GetData(as_instance = True)
817 if drug is None:
818 self._PRW_aim.unset_context(context = 'substance')
819 return
820
821
822
823
824
826 if self._DP_discontinued.GetData() is None:
827 self._PRW_discontinue_reason.Enable(False)
828 else:
829 self._PRW_discontinue_reason.Enable(True)
830
831
834
835
838
839
842
843
846
847
850
851
859
860
890
891
893 if self._CHBOX_long_term.IsChecked() is True:
894 self._PRW_duration.Enable(False)
895 self._BTN_discontinued_as_planned.Enable(False)
896 self._PRW_discontinue_reason.Enable(False)
897 else:
898 self._PRW_duration.Enable(True)
899 self._BTN_discontinued_as_planned.Enable(True)
900 self._PRW_discontinue_reason.Enable(True)
901
902 self.__refresh_precautions()
903
904
906 event.Skip()
907 if self._CHBOX_start_unknown.IsChecked() is True:
908 self._DP_started.Enable(False)
909 self._PRW_start_certainty.Enable(False)
910 else:
911 self._DP_started.Enable(True)
912 self._PRW_start_certainty.Enable(True)
913
914 self.__refresh_precautions()
915
916
926
927
929
930 comps = intake.containing_drug.components
931 if len(comps) > 1:
932 msg = _(
933 'This intake is part of a multi-component drug product:\n'
934 '\n'
935 ' %s\n'
936 '\n'
937 'Really delete all intakes related to this drug product ?'
938 ) % '\n '.join (
939 [ '%s %s%s' % (c['substance'], c['amount'], c.formatted_units) for c in comps ]
940 )
941 delete_all = gmGuiHelpers.gm_show_question (
942 title = _('Deleting medication / substance intake'),
943 question = msg
944 )
945 if not delete_all:
946 return
947
948 msg = _(
949 '\n'
950 '[%s]\n'
951 '\n'
952 'It may be prudent to edit (before deletion) the details\n'
953 'of this substance intake entry so as to leave behind\n'
954 'some indication of why it was deleted.\n'
955 ) % intake.format()
956
957 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
958 parent,
959 -1,
960 caption = _('Deleting medication / substance intake'),
961 question = msg,
962 button_defs = [
963 {'label': _('&Edit'), 'tooltip': _('Allow editing of substance intake entry before deletion.'), 'default': True},
964 {'label': _('&Delete'), 'tooltip': _('Delete immediately without editing first.')},
965 {'label': _('&Cancel'), 'tooltip': _('Abort. Do not delete or edit substance intake entry.')}
966 ]
967 )
968
969 edit_first = dlg.ShowModal()
970 dlg.DestroyLater()
971
972 if edit_first == wx.ID_CANCEL:
973 return
974
975 if edit_first == wx.ID_YES:
976 edit_intake_of_substance(parent = parent, substance = intake)
977 delete_it = gmGuiHelpers.gm_show_question (
978 aMessage = _('Now delete substance intake entry ?'),
979 aTitle = _('Deleting medication / substance intake')
980 )
981 else:
982 delete_it = True
983
984 if not delete_it:
985 return
986
987 gmMedication.delete_substance_intake(pk_intake = intake['pk_substance_intake'], delete_siblings = True)
988
989
991 ea = cSubstanceIntakeEAPnl(parent, -1, substance = substance)
992 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
993 dlg.SetTitle(gmTools.coalesce(substance, _('Adding medication/non-medication substance intake'), _('Editing medication/non-medication substance intake')))
994 dlg.left_extra_button = (
995 _('Allergy'),
996 _('Document an allergy against this substance.'),
997 ea.turn_into_allergy
998 )
999 dlg.SetSize((650,500))
1000 if dlg.ShowModal() == wx.ID_OK:
1001 dlg.DestroyLater()
1002 return True
1003 dlg.DestroyLater()
1004 return False
1005
1006
1007
1008
1036
1037
1039
1040 if parent is None:
1041 parent = wx.GetApp().GetTopWindow()
1042
1043
1044 dbcfg = gmCfg.cCfgSQL()
1045 option = 'form_templates.medication_list'
1046
1047 template = dbcfg.get2 (
1048 option = option,
1049 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
1050 bias = 'user'
1051 )
1052
1053 if template is None:
1054 template = configure_medication_list_template(parent = parent)
1055 if template is None:
1056 gmGuiHelpers.gm_show_error (
1057 aMessage = _('There is no medication list template configured.'),
1058 aTitle = _('Printing medication list')
1059 )
1060 return False
1061 else:
1062 try:
1063 name, ver = template.split(' - ')
1064 except:
1065 _log.exception('problem splitting medication list template name [%s]', template)
1066 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading medication list template.'), beep = True)
1067 return False
1068 template = gmForms.get_form_template(name_long = name, external_version = ver)
1069 if template is None:
1070 gmGuiHelpers.gm_show_error (
1071 aMessage = _('Cannot load medication list template [%s - %s]') % (name, ver),
1072 aTitle = _('Printing medication list')
1073 )
1074 return False
1075
1076
1077 meds_list = gmFormWidgets.generate_form_from_template (
1078 parent = parent,
1079 template = template,
1080 edit = False
1081 )
1082 if meds_list is None:
1083 return False
1084
1085
1086 return gmFormWidgets.act_on_generated_forms (
1087 parent = parent,
1088 forms = [meds_list],
1089 jobtype = 'medication_list',
1090
1091 episode_name = gmMedication.DEFAULT_MEDICATION_HISTORY_EPISODE,
1092 progress_note = _('generated medication list document'),
1093 review_copy_as_normal = True
1094 )
1095
1096
1125
1126
1128
1129 if parent is None:
1130 parent = wx.GetApp().GetTopWindow()
1131
1132 dbcfg = gmCfg.cCfgSQL()
1133 option = 'form_templates.prescription'
1134 template_name = dbcfg.get2 (
1135 option = option,
1136 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
1137 bias = 'user'
1138 )
1139
1140 if template_name is None:
1141 template = configure_prescription_template(parent = parent)
1142 if template is None:
1143 gmGuiHelpers.gm_show_error (
1144 aMessage = _('There is no prescription template configured.'),
1145 aTitle = _('Printing prescription')
1146 )
1147 return None
1148 return template
1149
1150 try:
1151 name, ver = template_name.split(' - ')
1152 except:
1153 _log.exception('problem splitting prescription template name [%s]', template_name)
1154 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading prescription template.'), beep = True)
1155 return False
1156 template = gmForms.get_form_template(name_long = name, external_version = ver)
1157 if template is None:
1158 gmGuiHelpers.gm_show_error (
1159 aMessage = _('Cannot load prescription template [%s - %s]') % (name, ver),
1160 aTitle = _('Printing prescription')
1161 )
1162 return None
1163 return template
1164
1165
1192
1193
1221
1222
1224
1225 if len(prescribed_drugs) == 0:
1226 return
1227
1228 curr_meds = [ i['pk_drug_product'] for i in emr.get_current_medications() if i['pk_drug_product'] is not None ]
1229 new_drugs = []
1230 for drug in prescribed_drugs:
1231 if drug['pk_drug_product'] not in curr_meds:
1232 new_drugs.append(drug)
1233
1234 if len(new_drugs) == 0:
1235 return
1236
1237 if parent is None:
1238 parent = wx.GetApp().GetTopWindow()
1239
1240 picker = gmListWidgets.cItemPickerDlg (
1241 parent,
1242 -1,
1243 msg = _(
1244 'These products have been prescribed but are not listed\n'
1245 'in the current medication list of this patient.\n'
1246 '\n'
1247 'Please select those you want added to the medication list.'
1248 )
1249 )
1250 picker.set_columns (
1251 columns = [_('Newly prescribed drugs')],
1252 columns_right = [_('Add to medication list')]
1253 )
1254 choices = [ ('%s %s (%s)' % (d['product'], d['l10n_preparation'], '; '.join(d['components']))) for d in new_drugs ]
1255 picker.set_choices (
1256 choices = choices,
1257 data = new_drugs
1258 )
1259 picker.ShowModal()
1260 drugs2add = picker.get_picks()
1261 picker.DestroyLater()
1262
1263 if drugs2add is None:
1264 return
1265
1266 if len(drugs2add) == 0:
1267 return
1268
1269 for drug in drugs2add:
1270
1271 intake = emr.add_substance_intake(pk_component = drug['components'][0]['pk_component'])
1272 if intake is None:
1273 continue
1274 intake['intake_is_approved_of'] = True
1275 intake.save()
1276
1277 return
1278
1279
1281 """A grid class for displaying current substance intake.
1282
1283 - does NOT listen to the currently active patient
1284 - thereby it can display any patient at any time
1285 """
1287
1288 wx.grid.Grid.__init__(self, *args, **kwargs)
1289
1290 self.__patient = None
1291 self.__row_data = {}
1292 self.__prev_row = None
1293 self.__prev_tooltip_row = None
1294 self.__prev_cell_0 = None
1295 self.__grouping_mode = 'issue'
1296 self.__filter_show_unapproved = True
1297 self.__filter_show_inactive = True
1298
1299 self.__grouping2col_labels = {
1300 'issue': [
1301 _('Health issue'),
1302 _('Substance'),
1303 _('Strength'),
1304 _('Schedule'),
1305 _('Timeframe'),
1306 _('Product'),
1307 _('Advice')
1308 ],
1309 'product': [
1310 _('Product'),
1311 _('Schedule'),
1312 _('Substance'),
1313 _('Strength'),
1314 _('Timeframe'),
1315 _('Health issue'),
1316 _('Advice')
1317 ],
1318 'episode': [
1319 _('Episode'),
1320 _('Substance'),
1321 _('Strength'),
1322 _('Schedule'),
1323 _('Timeframe'),
1324 _('Product'),
1325 _('Advice')
1326 ],
1327 'start': [
1328 _('Episode'),
1329 _('Substance'),
1330 _('Strength'),
1331 _('Schedule'),
1332 _('Timeframe'),
1333 _('Product'),
1334 _('Advice')
1335 ],
1336 }
1337
1338 self.__grouping2order_by_clauses = {
1339 'issue': 'pk_health_issue NULLS FIRST, substance, started',
1340 'episode': 'pk_health_issue NULLS FIRST, episode, substance, started',
1341 'product': 'product NULLS LAST, substance, started',
1342 'start': 'started DESC, substance, episode'
1343 }
1344
1345 self.__init_ui()
1346 self.__register_events()
1347
1348
1349
1350
1352
1353 sel_block_top_left = self.GetSelectionBlockTopLeft()
1354 sel_block_bottom_right = self.GetSelectionBlockBottomRight()
1355 sel_cols = self.GetSelectedCols()
1356 sel_rows = self.GetSelectedRows()
1357
1358 selected_cells = []
1359
1360
1361 selected_cells += self.GetSelectedCells()
1362
1363
1364 selected_cells += list (
1365 (row, col)
1366 for row in sel_rows
1367 for col in range(self.GetNumberCols())
1368 )
1369
1370
1371 selected_cells += list (
1372 (row, col)
1373 for row in range(self.GetNumberRows())
1374 for col in sel_cols
1375 )
1376
1377
1378 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()):
1379 selected_cells += [
1380 (row, col)
1381 for row in range(top_left[0], bottom_right[0] + 1)
1382 for col in range(top_left[1], bottom_right[1] + 1)
1383 ]
1384
1385 return set(selected_cells)
1386
1387
1389 rows = {}
1390
1391 for row, col in self.get_selected_cells():
1392 rows[row] = True
1393
1394 return rows.keys()
1395
1396
1399
1400
1402 return self.__row_data.values()
1403
1404
1406
1407 self.empty_grid()
1408
1409 if self.__patient is None:
1410 return
1411
1412 emr = self.__patient.emr
1413 meds = emr.get_current_medications (
1414 order_by = self.__grouping2order_by_clauses[self.__grouping_mode],
1415 include_unapproved = self.__filter_show_unapproved,
1416 include_inactive = self.__filter_show_inactive
1417 )
1418 if not meds:
1419 return
1420
1421 self.BeginBatch()
1422
1423
1424 labels = self.__grouping2col_labels[self.__grouping_mode]
1425 if self.__filter_show_unapproved:
1426 self.AppendCols(numCols = len(labels) + 1)
1427 else:
1428 self.AppendCols(numCols = len(labels))
1429 for col_idx in range(len(labels)):
1430 self.SetColLabelValue(col_idx, labels[col_idx])
1431 if self.__filter_show_unapproved:
1432
1433 self.SetColLabelValue(len(labels), '')
1434 self.SetColSize(len(labels), 40)
1435
1436 self.AppendRows(numRows = len(meds))
1437
1438
1439 for row_idx in range(len(meds)):
1440 med = meds[row_idx]
1441 self.__row_data[row_idx] = med
1442
1443 if med['is_currently_active'] is True:
1444 atcs = []
1445 if med['atc_substance'] is not None:
1446 atcs.append(med['atc_substance'])
1447 allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (med['substance'],))
1448 if allg not in [None, False]:
1449 attr = self.GetOrCreateCellAttr(row_idx, 0)
1450 if allg['type'] == 'allergy':
1451 attr.SetTextColour('red')
1452 else:
1453
1454
1455
1456 attr.SetTextColour('magenta')
1457 self.SetRowAttr(row_idx, attr)
1458 else:
1459 attr = self.GetOrCreateCellAttr(row_idx, 0)
1460 attr.SetTextColour('grey')
1461 self.SetRowAttr(row_idx, attr)
1462
1463 if self.__grouping_mode in ['episode', 'start']:
1464 if med['pk_episode'] is None:
1465 self.__prev_cell_0 = None
1466 epi = gmTools.u_diameter
1467 else:
1468 if self.__prev_cell_0 == med['episode']:
1469 epi = ''
1470 else:
1471 self.__prev_cell_0 = med['episode']
1472 epi = gmTools.coalesce(med['episode'], '')
1473 self.SetCellValue(row_idx, 0, gmTools.wrap(text = epi, width = 40))
1474
1475 self.SetCellValue(row_idx, 1, med['substance'])
1476 self.SetCellValue(row_idx, 2, '%s %s' % (med['amount'], med['unit']))
1477 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], ''))
1478 self.SetCellValue(row_idx, 4, med.medically_formatted_start_end)
1479
1480 if med['pk_drug_product'] is None:
1481 product = '%s (%s)' % (gmTools.u_diameter, med['l10n_preparation'])
1482 else:
1483 if med['is_fake_product']:
1484 product = '%s (%s)' % (
1485 gmTools.coalesce(med['product'], '', _('%s <fake>')),
1486 med['l10n_preparation']
1487 )
1488 else:
1489 product = '%s (%s)' % (
1490 gmTools.coalesce(med['product'], ''),
1491 med['l10n_preparation']
1492 )
1493 self.SetCellValue(row_idx, 5, gmTools.wrap(text = product, width = 35))
1494
1495 elif self.__grouping_mode == 'issue':
1496 if med['pk_health_issue'] is None:
1497 self.__prev_cell_0 = None
1498 issue = '%s%s' % (
1499 gmTools.u_diameter,
1500 gmTools.coalesce(med['episode'], '', ' (%s)')
1501 )
1502 else:
1503 if self.__prev_cell_0 == med['health_issue']:
1504 issue = ''
1505 else:
1506 self.__prev_cell_0 = med['health_issue']
1507 issue = med['health_issue']
1508 self.SetCellValue(row_idx, 0, gmTools.wrap(text = issue, width = 40))
1509
1510 self.SetCellValue(row_idx, 1, med['substance'])
1511 self.SetCellValue(row_idx, 2, '%s %s' % (med['amount'], med['unit']))
1512 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], ''))
1513 self.SetCellValue(row_idx, 4, med.medically_formatted_start_end)
1514
1515 if med['pk_drug_product'] is None:
1516 product = '%s (%s)' % (gmTools.u_diameter, med['l10n_preparation'])
1517 else:
1518 if med['is_fake_product']:
1519 product = '%s (%s)' % (
1520 gmTools.coalesce(med['product'], '', _('%s <fake>')),
1521 med['l10n_preparation']
1522 )
1523 else:
1524 product = '%s (%s)' % (
1525 gmTools.coalesce(med['product'], ''),
1526 med['l10n_preparation']
1527 )
1528 self.SetCellValue(row_idx, 5, gmTools.wrap(text = product, width = 35))
1529
1530 elif self.__grouping_mode == 'product':
1531
1532 if med['pk_drug_product'] is None:
1533 self.__prev_cell_0 = None
1534 product = '%s (%s)' % (
1535 gmTools.u_diameter,
1536 med['l10n_preparation']
1537 )
1538 else:
1539 if self.__prev_cell_0 == med['product']:
1540 product = ''
1541 else:
1542 self.__prev_cell_0 = med['product']
1543 if med['is_fake_product']:
1544 product = '%s (%s)' % (
1545 gmTools.coalesce(med['product'], '', _('%s <fake>')),
1546 med['l10n_preparation']
1547 )
1548 else:
1549 product = '%s (%s)' % (
1550 gmTools.coalesce(med['product'], ''),
1551 med['l10n_preparation']
1552 )
1553 self.SetCellValue(row_idx, 0, gmTools.wrap(text = product, width = 35))
1554
1555 self.SetCellValue(row_idx, 1, gmTools.coalesce(med['schedule'], ''))
1556 self.SetCellValue(row_idx, 2, med['substance'])
1557 self.SetCellValue(row_idx, 3, '%s %s' % (med['amount'], med['unit']))
1558 self.SetCellValue(row_idx, 4, med.medically_formatted_start_end)
1559
1560 if med['pk_health_issue'] is None:
1561 issue = '%s%s' % (
1562 gmTools.u_diameter,
1563 gmTools.coalesce(med['episode'], '', ' (%s)')
1564 )
1565 else:
1566 issue = gmTools.coalesce(med['health_issue'], '')
1567 self.SetCellValue(row_idx, 5, gmTools.wrap(text = issue, width = 40))
1568
1569 else:
1570 raise ValueError('unknown grouping mode [%s]' % self.__grouping_mode)
1571
1572 if med['notes'] is not None:
1573 self.SetCellValue(row_idx, 6, gmTools.wrap(text = med['notes'], width = 50))
1574
1575 if self.__filter_show_unapproved:
1576 self.SetCellValue (
1577 row_idx,
1578 len(labels),
1579 gmTools.bool2subst(med['intake_is_approved_of'], gmTools.u_checkmark_thin, gmTools.u_frowning_face, '?')
1580 )
1581 font = self.GetCellFont(row_idx, len(labels))
1582 font.SetPointSize(font.GetPointSize() + 2)
1583 self.SetCellFont(row_idx, len(labels), font)
1584
1585
1586
1587 self.AutoSize()
1588 self.EndBatch()
1589
1591 self.BeginBatch()
1592 self.ClearGrid()
1593
1594
1595 if self.GetNumberRows() > 0:
1596 self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
1597 if self.GetNumberCols() > 0:
1598 self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
1599 self.EndBatch()
1600 self.__row_data = {}
1601 self.__prev_cell_0 = None
1602
1603
1605
1606 if len(self.__row_data) == 0:
1607 return
1608
1609 sel_rows = self.get_selected_rows()
1610 if len(sel_rows) != 1:
1611 return
1612
1613 drug_db = gmSubstanceMgmtWidgets.get_drug_database(patient = self.__patient)
1614 if drug_db is None:
1615 return
1616
1617 intake = self.get_selected_data()[0]
1618 if intake['product'] is None:
1619 drug_db.show_info_on_substance(substance_intake = intake)
1620 else:
1621 drug_db.show_info_on_drug(substance_intake = intake)
1622
1623
1631
1632
1635
1636
1646
1647
1653
1667
1670
1684
1685
1699
1700
1716
1720
1821
1822
1823
1824
1826 self.CreateGrid(0, 1)
1827 self.EnableEditing(0)
1828 self.EnableDragGridSize(1)
1829 self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows)
1830
1831 self.SetColLabelAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTER)
1832
1833 self.SetRowLabelSize(0)
1834 self.SetRowLabelAlignment(horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
1835
1836
1837
1838
1840 return self.__patient
1841
1845
1846 patient = property(_get_patient, _set_patient)
1847
1849 return self.__grouping_mode
1850
1854
1855 grouping_mode = property(_get_grouping_mode, _set_grouping_mode)
1856
1858 return self.__filter_show_unapproved
1859
1863
1864 filter_show_unapproved = property(_get_filter_show_unapproved, _set_filter_show_unapproved)
1865
1867 return self.__filter_show_inactive
1868
1872
1873 filter_show_inactive = property(_get_filter_show_inactive, _set_filter_show_inactive)
1874
1875
1876
1878
1879 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
1880
1881
1882
1883
1884 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
1885
1887 """Calculate where the mouse is and set the tooltip dynamically."""
1888
1889
1890
1891 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905 row, col = self.XYToCell(x, y)
1906
1907 if row == self.__prev_tooltip_row:
1908 return
1909
1910 self.__prev_tooltip_row = row
1911
1912 try:
1913 evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row))
1914 except KeyError:
1915 pass
1916
1921
1922
1941
1942
1954
1955 gmCfgWidgets.configure_string_option (
1956 message = _(
1957 'GNUmed will use this URL to access a website which lets\n'
1958 'you report an adverse drug reaction (ADR).\n'
1959 '\n'
1960 'If you leave this empty it will fall back\n'
1961 'to an URL for reporting ADRs in Germany.'
1962 ),
1963 option = 'external.urls.report_ADR',
1964 bias = 'user',
1965 default_value = gmMedication.URL_drug_adr_german_default,
1966 validator = is_valid
1967 )
1968
1969
1970 from Gnumed.wxGladeWidgets import wxgCurrentSubstancesPnl
1971
1972 -class cCurrentSubstancesPnl(wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl, gmRegetMixin.cRegetOnPaintMixin):
1973
1974 """Panel holding a grid with current substances. Used as notebook page."""
1975
1977
1978 wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl.__init__(self, *args, **kwargs)
1979 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1980
1981 self.__grouping_choice_labels = [
1982 {'label': _('Health issue'), 'data': 'issue'} ,
1983 {'label': _('Drug product'), 'data': 'product'},
1984 {'label': _('Episode'), 'data': 'episode'},
1985 {'label': _('Started'), 'data': 'start'}
1986 ]
1987 self.__lab_panel = None
1988
1989 self.__init_ui()
1990 self.__register_interests()
1991
1992
1994 self._CHCE_grouping.Clear()
1995 for option in self.__grouping_choice_labels:
1996 self._CHCE_grouping.Append(option['label'], option['data'])
1997 self._CHCE_grouping.SetSelection(0)
1998
1999 tt = self._BTN_heart.GetToolTipText()
2000 try:
2001 self._BTN_heart.SetToolTip(tt % gmMedication.URL_long_qt)
2002 except TypeError:
2003 _log.exception('translation error: %s', tt)
2004
2005 tt = self._BTN_kidneys.GetToolTipText()
2006 try:
2007 self._BTN_kidneys.SetToolTip(tt % gmMedication.URL_renal_insufficiency)
2008 except TypeError:
2009 _log.exception('translation error: %s', tt)
2010
2011
2012
2013
2015 """Populate cells with data from model."""
2016 pat = gmPerson.gmCurrentPatient()
2017 if pat.connected:
2018 self._grid_substances.patient = pat
2019 self.__refresh_gfr(pat)
2020 self.__refresh_lab(patient = pat)
2021 else:
2022 self._grid_substances.patient = None
2023 self.__clear_gfr()
2024 self.__refresh_lab(patient = None)
2025 return True
2026
2027
2029
2030 self._GSZR_lab.Clear(True)
2031 self._HLINE_lab.Hide()
2032
2033 if patient is None:
2034 self.Layout()
2035 return
2036
2037 emr = patient.emr
2038 most_recent_results = {}
2039
2040
2041 loincs2monitor = set()
2042 loincs2monitor_data = {}
2043 loinc_max_age = {}
2044 loinc_max_age_str = {}
2045 for intake in self._grid_substances.get_row_data():
2046 for l in intake['loincs']:
2047 loincs2monitor.add(l['loinc'])
2048 loincs2monitor_data[l['loinc']] = {
2049 'substance': intake['substance'],
2050 'comment': l['comment']
2051 }
2052 if l['max_age_in_secs'] is not None:
2053 try:
2054 if loinc_max_age[l['loinc']] > l['max_age_in_secs']:
2055 loinc_max_age[l['loinc']] = l['max_age_in_secs']
2056 loinc_max_age_str[l['loinc']] = l['max_age_str']
2057 except KeyError:
2058 loinc_max_age[l['loinc']] = l['max_age_in_secs']
2059 loinc_max_age_str[l['loinc']] = l['max_age_str']
2060 loincs2monitor_missing = loincs2monitor.copy()
2061 for loinc in loincs2monitor:
2062 result = emr.get_most_recent_results_in_loinc_group (
2063 loincs = [loinc],
2064 no_of_results = 1,
2065 consider_meta_type = True
2066 )
2067 if result is None:
2068 continue
2069 loincs2monitor_missing.remove(loinc)
2070
2071 most_recent_results[result['pk_test_result']] = result
2072
2073
2074 if self.__lab_panel is not None:
2075 for result in self.__lab_panel.get_most_recent_results (
2076 pk_patient = patient.ID,
2077 order_by = 'unified_abbrev',
2078 group_by_meta_type = True
2079 ):
2080 try: loincs2monitor_missing.remove(result['loinc_tt'])
2081 except KeyError: pass
2082 try: loincs2monitor_missing.remove(result['loinc_meta'])
2083 except KeyError: pass
2084
2085 most_recent_results[result['pk_test_result']] = result
2086
2087
2088 gfr = emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, no_of_results = 1)
2089 crea = emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, no_of_results = 1)
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 gfr = patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, no_of_results = 1)
2265 if gfr is None:
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 msg = '%s: %s %s (%s)\n' % (
2298 gfr['unified_abbrev'],
2299 gfr['unified_val'],
2300 gmTools.coalesce(gfr['abnormality_indicator'], '', ' (%s)'),
2301 gmDateTime.pydt_strftime (
2302 gfr['clin_when'],
2303 format = '%b %Y'
2304 )
2305 )
2306 tt = _('GFR reported by path lab')
2307
2308 self._LBL_gfr.SetLabel(msg)
2309 self._LBL_gfr.SetToolTip(tt)
2310 self._LBL_gfr.Refresh()
2311 self.Layout()
2312
2313
2315 self._LBL_gfr.SetLabel(_('GFR: ?'))
2316 self._LBL_gfr.Refresh()
2317 self.Layout()
2318
2319
2320
2321
2323 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
2324 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
2325 gmDispatcher.connect(signal = 'clin.substance_intake_mod_db', receiver = self._schedule_data_reget)
2326 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._on_test_result_mod)
2327
2328
2331
2332
2346
2348 self._schedule_data_reget()
2349
2352
2355
2358
2361
2364
2366 event.Skip()
2367 selected_item_idx = self._CHCE_grouping.GetSelection()
2368 if selected_item_idx is wx.NOT_FOUND:
2369 return
2370 self._grid_substances.grouping_mode = self._CHCE_grouping.GetClientData(selected_item_idx)
2371
2374
2377
2380
2383
2386
2389
2392
2395
2396
2397
2398
2399 if __name__ == '__main__':
2400
2401 if len(sys.argv) < 2:
2402 sys.exit()
2403
2404 if sys.argv[1] != 'test':
2405 sys.exit()
2406
2407 from Gnumed.business import gmPersonSearch
2408
2409 pat = gmPersonSearch.ask_for_patient()
2410 if pat is None:
2411 sys.exit()
2412 gmPerson.set_active_patient(patient = pat)
2413
2414
2415 app = wx.PyWidgetTester(size = (600, 300))
2416 app.SetWidget(cSubstanceIntakeObjectPhraseWheel, -1)
2417 app.MainLoop()
2418
2419