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 gmDispatcher.send(signal = 'statustext', msg = _('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 gmDispatcher.send(signal = 'statustext', msg = _('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 gmDispatcher.send(signal = 'statustext', msg = _('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 gmDispatcher.send('statustext', msg = _('Cannot add duplicate of (maybe inactive) substance intake.'), beep = True)
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.Destroy()
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 = (substance is not None))
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.Destroy()
1002 return True
1003 dlg.Destroy()
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.Destroy()
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
1943 from Gnumed.wxGladeWidgets import wxgCurrentSubstancesPnl
1944
1945 -class cCurrentSubstancesPnl(wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl, gmRegetMixin.cRegetOnPaintMixin):
1946
1947 """Panel holding a grid with current substances. Used as notebook page."""
1948
1950
1951 wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl.__init__(self, *args, **kwargs)
1952 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1953
1954 self.__grouping_choice_labels = [
1955 {'label': _('Health issue'), 'data': 'issue'} ,
1956 {'label': _('Drug product'), 'data': 'product'},
1957 {'label': _('Episode'), 'data': 'episode'},
1958 {'label': _('Started'), 'data': 'start'}
1959 ]
1960 self.__lab_panel = None
1961
1962 self.__init_ui()
1963 self.__register_interests()
1964
1965
1967 self._CHCE_grouping.Clear()
1968 for option in self.__grouping_choice_labels:
1969 self._CHCE_grouping.Append(option['label'], option['data'])
1970 self._CHCE_grouping.SetSelection(0)
1971
1972 tt = self._BTN_heart.GetToolTipText()
1973 try:
1974 self._BTN_heart.SetToolTip(tt % gmMedication.URL_long_qt)
1975 except TypeError:
1976 _log.exception('translation error: %s', tt)
1977
1978 tt = self._BTN_kidneys.GetToolTipText()
1979 try:
1980 self._BTN_kidneys.SetToolTip(tt % gmMedication.URL_renal_insufficiency)
1981 except TypeError:
1982 _log.exception('translation error: %s', tt)
1983
1984
1985
1986
1988 """Populate cells with data from model."""
1989 pat = gmPerson.gmCurrentPatient()
1990 if pat.connected:
1991 self._grid_substances.patient = pat
1992 self.__refresh_gfr(pat)
1993 self.__refresh_lab(patient = pat)
1994 else:
1995 self._grid_substances.patient = None
1996 self.__clear_gfr()
1997 self.__refresh_lab(patient = None)
1998 return True
1999
2000
2002
2003 self._GSZR_lab.Clear(True)
2004 self._HLINE_lab.Hide()
2005
2006 if patient is None:
2007 self.Layout()
2008 return
2009
2010 emr = patient.emr
2011 most_recent_results = {}
2012
2013
2014 loincs2monitor = set()
2015 loincs2monitor_data = {}
2016 loinc_max_age = {}
2017 loinc_max_age_str = {}
2018 for intake in self._grid_substances.get_row_data():
2019 for l in intake['loincs']:
2020 loincs2monitor.add(l['loinc'])
2021 loincs2monitor_data[l['loinc']] = {
2022 'substance': intake['substance'],
2023 'comment': l['comment']
2024 }
2025 if l['max_age_in_secs'] is not None:
2026 try:
2027 if loinc_max_age[l['loinc']] > l['max_age_in_secs']:
2028 loinc_max_age[l['loinc']] = l['max_age_in_secs']
2029 loinc_max_age_str[l['loinc']] = l['max_age_str']
2030 except KeyError:
2031 loinc_max_age[l['loinc']] = l['max_age_in_secs']
2032 loinc_max_age_str[l['loinc']] = l['max_age_str']
2033 loincs2monitor_missing = loincs2monitor.copy()
2034 for loinc in loincs2monitor:
2035 result = emr.get_most_recent_results_in_loinc_group (
2036 loincs = [loinc],
2037 no_of_results = 1,
2038 consider_meta_type = True
2039 )
2040 if result is None:
2041 continue
2042 loincs2monitor_missing.remove(loinc)
2043
2044 most_recent_results[result['pk_test_result']] = result
2045
2046
2047 if self.__lab_panel is not None:
2048 for result in self.__lab_panel.get_most_recent_results (
2049 pk_patient = patient.ID,
2050 order_by = 'unified_abbrev',
2051 group_by_meta_type = True
2052 ):
2053 try: loincs2monitor_missing.remove(result['loinc_tt'])
2054 except KeyError: pass
2055 try: loincs2monitor_missing.remove(result['loinc_meta'])
2056 except KeyError: pass
2057
2058 most_recent_results[result['pk_test_result']] = result
2059
2060
2061 gfr = emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, no_of_results = 1)
2062 crea = emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, no_of_results = 1)
2063 edc = emr.EDC
2064
2065
2066 if edc is not None:
2067 if emr.EDC_is_fishy:
2068 lbl = wx.StaticText(self, -1, _('EDC (!?!):'))
2069 val = wx.StaticText(self, -1, gmDateTime.pydt_strftime(edc, format = '%Y %b %d'))
2070 else:
2071 lbl = wx.StaticText(self, -1, _('EDC:'))
2072 val = wx.StaticText(self, -1, gmDateTime.pydt_strftime(edc, format = '%Y %b %d'))
2073 lbl.SetForegroundColour('blue')
2074 szr = wx.BoxSizer(wx.HORIZONTAL)
2075 szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3)
2076 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL)
2077 self._GSZR_lab.Add(szr)
2078
2079
2080 if crea is None:
2081 gfr_3_months_older_than_crea = False
2082 if gfr is not None:
2083 most_recent_results = [gfr] + most_recent_results
2084 elif gfr is None:
2085 gfr_3_months_older_than_crea = True
2086 else:
2087 three_months = pydt.timedelta(weeks = 14)
2088 gfr_3_months_older_than_crea = (crea['clin_when'] - gfr['clin_when']) > three_months
2089 if not gfr_3_months_older_than_crea:
2090 most_recent_results = [gfr] + most_recent_results
2091
2092
2093 now = gmDateTime.pydt_now_here()
2094 if gfr_3_months_older_than_crea:
2095 calc = gmClinicalCalculator.cClinicalCalculator()
2096 calc.patient = patient
2097 gfr = calc.eGFR
2098 if gfr.numeric_value is None:
2099 gfr_msg = '?'
2100 else:
2101 gfr_msg = _('%.1f (%s ago)') % (
2102 gfr.numeric_value,
2103 gmDateTime.format_interval_medically(now - gfr.date_valid)
2104 )
2105 lbl = wx.StaticText(self, -1, _('eGFR:'))
2106 lbl.SetForegroundColour('blue')
2107 val = wx.StaticText(self, -1, gfr_msg)
2108 tts = []
2109 for egfr in calc.eGFRs:
2110 if egfr.numeric_value is None:
2111 continue
2112 tts.append(egfr.format (
2113 left_margin = 0,
2114 width = 50,
2115 eol = '\n',
2116 with_formula = False,
2117 with_warnings = True,
2118 with_variables = False,
2119 with_sub_results = False,
2120 return_list = False
2121 ))
2122 val.SetToolTip('\n'.join(tts))
2123 szr = wx.BoxSizer(wx.HORIZONTAL)
2124 szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3)
2125 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL)
2126 self._GSZR_lab.Add(szr)
2127
2128
2129 for pk_result in most_recent_results:
2130 result = most_recent_results[pk_result]
2131
2132 lbl = wx.StaticText(self, -1, '%s:' % result['unified_abbrev'])
2133 lbl.SetForegroundColour('blue')
2134
2135 indicate_attention = False
2136 if result.is_considered_abnormal:
2137 indicate_attention = True
2138
2139 max_age = None
2140 try:
2141 max_age = loinc_max_age[result['loinc_tt']]
2142 max_age_str = loinc_max_age_str[result['loinc_tt']]
2143 except KeyError:
2144 try:
2145 max_age = loinc_max_age[result['loinc_meta']]
2146 max_age_str = loinc_max_age_str[result['loinc_meta']]
2147 except KeyError:
2148 pass
2149 subst2monitor = None
2150 try:
2151 subst2monitor = loincs2monitor_data[result['loinc_tt']]['substance']
2152 except KeyError:
2153 try:
2154 subst2monitor = loincs2monitor_data[result['loinc_meta']]['substance']
2155 except KeyError:
2156 pass
2157 monitor_comment = None
2158 try:
2159 monitor_comment = loincs2monitor_data[result['loinc_tt']]['comment']
2160 except KeyError:
2161 try:
2162 monitor_comment = loincs2monitor_data[result['loinc_meta']]['comment']
2163 except KeyError:
2164 pass
2165 result_age = now - result['clin_when']
2166 unhappy_reasons = []
2167 if result.is_considered_abnormal:
2168 indicator = result.formatted_abnormality_indicator
2169 if indicator == '':
2170 unhappy_reasons.append(_(' - abnormal'))
2171 else:
2172 unhappy_reasons.append(_(' - abnormal: %s') % indicator)
2173 if max_age is not None:
2174 if result_age.total_seconds() > max_age:
2175 unhappy_reasons.append(_(' - too old: %s ago (max: %s)') % (
2176 gmDateTime.format_interval_medically(result_age),
2177 max_age_str
2178 ))
2179
2180 tt = [_('Most recent: %s ago') % gmDateTime.format_interval_medically(result_age)]
2181 if subst2monitor is not None:
2182 tt.append(_('Why monitor: %s') % subst2monitor)
2183 if monitor_comment is not None:
2184 tt.append(' %s' % monitor_comment)
2185 if len(unhappy_reasons) > 0:
2186 indicate_attention = True
2187 tt.append(_('Problems:'))
2188 tt.extend(unhappy_reasons)
2189 tt = '%s\n\n%s' % (
2190 '\n'.join(tt),
2191 result.format()
2192 )
2193
2194 val = wx.StaticText(self, -1, '%s%s%s' % (
2195 result['unified_val'],
2196 gmTools.coalesce(result['val_unit'], '', ' %s'),
2197 gmTools.bool2subst(indicate_attention, gmTools.u_frowning_face, '', '')
2198 ))
2199 val.SetToolTip(tt)
2200 if result.is_considered_abnormal:
2201 val.SetForegroundColour('red')
2202 szr = wx.BoxSizer(wx.HORIZONTAL)
2203 szr.Add(lbl, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3)
2204 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL)
2205 self._GSZR_lab.Add(szr)
2206
2207
2208
2209 for loinc in loincs2monitor_missing:
2210
2211 loinc_data = gmLOINC.loinc2data(loinc)
2212 if loinc_data is None:
2213 loinc_str = loinc
2214 else:
2215 loinc_str = loinc_data['term']
2216 val = wx.StaticText(self, -1, '%s!' % loinc_str)
2217 tt = [
2218 _('No test result for: %s (%s)') % (loinc_str, loinc),
2219 '',
2220 _('Why monitor: %s' % loincs2monitor_data[loinc]['substance'])
2221 ]
2222 try:
2223 tt.append(' %s' % loincs2monitor_data[loinc]['comment'])
2224 except KeyError:
2225 pass
2226 val.SetToolTip('\n'.join(tt))
2227 val.SetForegroundColour('orange')
2228 szr = wx.BoxSizer(wx.HORIZONTAL)
2229 szr.Add(val, 1, wx.ALIGN_CENTER_VERTICAL)
2230 self._GSZR_lab.Add(szr)
2231
2232 self._HLINE_lab.Show()
2233 self.Layout()
2234
2235
2237 gfr = patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, no_of_results = 1)
2238 if gfr is None:
2239 calc = gmClinicalCalculator.cClinicalCalculator()
2240 calc.patient = patient
2241 gfr = calc.eGFR
2242 if gfr.numeric_value is None:
2243 msg = _('GFR: ?')
2244 tt = gfr.message
2245 else:
2246 msg = _('eGFR: %.1f (%s)') % (
2247 gfr.numeric_value,
2248 gmDateTime.pydt_strftime (
2249 gfr.date_valid,
2250 format = '%b %Y'
2251 )
2252 )
2253 egfrs = calc.eGFRs
2254 tts = []
2255 for egfr in egfrs:
2256 if egfr.numeric_value is None:
2257 continue
2258 tts.append(egfr.format (
2259 left_margin = 0,
2260 width = 50,
2261 eol = '\n',
2262 with_formula = False,
2263 with_warnings = True,
2264 with_variables = False,
2265 with_sub_results = False,
2266 return_list = False
2267 ))
2268 tt = '\n'.join(tts)
2269 else:
2270 msg = '%s: %s %s (%s)\n' % (
2271 gfr['unified_abbrev'],
2272 gfr['unified_val'],
2273 gmTools.coalesce(gfr['abnormality_indicator'], '', ' (%s)'),
2274 gmDateTime.pydt_strftime (
2275 gfr['clin_when'],
2276 format = '%b %Y'
2277 )
2278 )
2279 tt = _('GFR reported by path lab')
2280
2281 self._LBL_gfr.SetLabel(msg)
2282 self._LBL_gfr.SetToolTip(tt)
2283 self._LBL_gfr.Refresh()
2284 self.Layout()
2285
2286
2288 self._LBL_gfr.SetLabel(_('GFR: ?'))
2289 self._LBL_gfr.Refresh()
2290 self.Layout()
2291
2292
2293
2294
2296 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
2297 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
2298 gmDispatcher.connect(signal = 'clin.substance_intake_mod_db', receiver = self._schedule_data_reget)
2299 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._on_test_result_mod)
2300
2301
2304
2305
2319
2321 self._schedule_data_reget()
2322
2325
2328
2331
2334
2337
2339 event.Skip()
2340 selected_item_idx = self._CHCE_grouping.GetSelection()
2341 if selected_item_idx is wx.NOT_FOUND:
2342 return
2343 self._grid_substances.grouping_mode = self._CHCE_grouping.GetClientData(selected_item_idx)
2344
2347
2350
2353
2356
2359
2362
2365
2368
2369
2370
2371
2372 if __name__ == '__main__':
2373
2374 if len(sys.argv) < 2:
2375 sys.exit()
2376
2377 if sys.argv[1] != 'test':
2378 sys.exit()
2379
2380 from Gnumed.business import gmPersonSearch
2381
2382 pat = gmPersonSearch.ask_for_patient()
2383 if pat is None:
2384 sys.exit()
2385 gmPerson.set_active_patient(patient = pat)
2386
2387
2388 app = wx.PyWidgetTester(size = (600, 300))
2389 app.SetWidget(cSubstanceIntakeObjectPhraseWheel, -1)
2390 app.MainLoop()
2391
2392