1 """GNUmed immunisation/vaccination widgets.
2
3 Modelled after Richard Terry's design document.
4
5 copyright: authors
6 """
7
8 __author__ = "R.Terry, S.J.Tan, K.Hilbert"
9 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
10
11 import sys
12 import logging
13
14
15 import wx
16
17
18 if __name__ == '__main__':
19 sys.path.insert(0, '../../')
20 from Gnumed.pycommon import gmDispatcher
21 from Gnumed.pycommon import gmMatchProvider
22 from Gnumed.pycommon import gmTools
23 from Gnumed.pycommon import gmI18N
24 from Gnumed.pycommon import gmCfg
25 from Gnumed.pycommon import gmCfg2
26 from Gnumed.pycommon import gmDateTime
27 from Gnumed.pycommon import gmNetworkTools
28 from Gnumed.pycommon import gmPrinting
29 from Gnumed.pycommon import gmPG2
30
31 from Gnumed.business import gmPerson
32 from Gnumed.business import gmVaccination
33 from Gnumed.business import gmPraxis
34 from Gnumed.business import gmProviderInbox
35
36 from Gnumed.wxpython import gmPhraseWheel
37 from Gnumed.wxpython import gmTerryGuiParts
38 from Gnumed.wxpython import gmRegetMixin
39 from Gnumed.wxpython import gmGuiHelpers
40 from Gnumed.wxpython import gmEditArea
41 from Gnumed.wxpython import gmListWidgets
42 from Gnumed.wxpython import gmFormWidgets
43 from Gnumed.wxpython import gmMacro
44 from Gnumed.wxpython import gmAuthWidgets
45 from Gnumed.wxpython import gmSubstanceMgmtWidgets
46
47
48 _log = logging.getLogger('gm.vacc')
49
50
51
52
54
55 dbo_conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Regenerating generic vaccines'))
56 if dbo_conn is None:
57 return False
58
59 wx.BeginBusyCursor()
60 _cfg = gmCfg2.gmCfgData()
61 sql_script = gmVaccination.write_generic_vaccine_sql (
62 'client-%s' % _cfg.get(option = 'client_version'),
63 include_indications_mapping = False
64 )
65 _log.debug('regenerating generic vaccines, SQL script: %s', sql_script)
66 if not gmPG2.run_sql_script(sql_script, conn = dbo_conn):
67 wx.EndBusyCursor()
68 gmGuiHelpers.gm_show_warning (
69 aMessage = _('Error regenerating generic vaccines.\n\nSee [%s]') % sql_script,
70 aTitle = _('Regenerating generic vaccines')
71 )
72 return False
73
74 gmDispatcher.send(signal = 'statustext', msg = _('Successfully regenerated generic vaccines ...'), beep = False)
75 wx.EndBusyCursor()
76 return True
77
78
79 -def edit_vaccine(parent=None, vaccine=None, single_entry=True):
90
91
132
133
134 def edit(vaccine=None):
135 return edit_vaccine(parent = parent, vaccine = vaccine, single_entry = True)
136
137
138 def get_tooltip(vaccine):
139 if vaccine is None:
140 return None
141 return '\n'.join(vaccine.format())
142
143
144 def refresh(lctrl):
145 vaccines = gmVaccination.get_vaccines(order_by = 'vaccine')
146
147 items = [ [
148 '%s' % v['pk_drug_product'],
149 '%s%s' % (
150 v['vaccine'],
151 gmTools.bool2subst (
152 v['is_fake_vaccine'],
153 ' (%s)' % _('fake'),
154 ''
155 )
156 ),
157 v['l10n_preparation'],
158 gmTools.coalesce(v['atc_code'], ''),
159 '%s - %s' % (
160 gmTools.coalesce(v['min_age'], '?'),
161 gmTools.coalesce(v['max_age'], '?'),
162 ),
163 gmTools.coalesce(v['comment'], '')
164 ] for v in vaccines ]
165 lctrl.set_string_items(items)
166 lctrl.set_data(vaccines)
167
168
169 gmListWidgets.get_choices_from_list (
170 parent = parent,
171 caption = _('Showing vaccine details'),
172 columns = [ '#', _('Vaccine'), _('Preparation'), _('ATC'), _('Age range'), _('Comment') ],
173 single_selection = True,
174 refresh_callback = refresh,
175 edit_callback = edit,
176 new_callback = edit,
177
178 list_tooltip_callback = get_tooltip,
179 left_extra_button = (_('Products'), _('Manage drug products'), manage_drug_products)
180 )
181
182
184
186
187 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
188
189 context = {
190 'ctxt_vaccine': {
191 'where_part': 'AND pk_vaccine = %(pk_vaccine)s',
192 'placeholder': 'pk_vaccine'
193 }
194 }
195
196 query = """
197 SELECT data, field_label, list_label FROM (
198
199 SELECT distinct on (field_label)
200 data,
201 field_label,
202 list_label,
203 rank
204 FROM ((
205 -- batch_no by vaccine
206 SELECT
207 batch_no AS data,
208 batch_no AS field_label,
209 batch_no || ' (' || vaccine || ')' AS list_label,
210 1 as rank
211 FROM
212 clin.v_vaccinations
213 WHERE
214 batch_no %(fragment_condition)s
215 %(ctxt_vaccine)s
216 ) UNION ALL (
217 -- batch_no for any vaccine
218 SELECT
219 batch_no AS data,
220 batch_no AS field_label,
221 batch_no || ' (' || vaccine || ')' AS list_label,
222 2 AS rank
223 FROM
224 clin.v_vaccinations
225 WHERE
226 batch_no %(fragment_condition)s
227 )
228
229 ) AS matching_batch_nos
230
231 ) as unique_matches
232
233 ORDER BY rank, list_label
234 LIMIT 25
235 """
236 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = context)
237 mp.setThresholds(1, 2, 3)
238 self.matcher = mp
239
240 self.unset_context(context = 'pk_vaccine')
241 self.SetToolTip(_('Enter or select the batch/lot number of the vaccine used.'))
242 self.selection_only = False
243
244
246
248
249 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
250
251
252 query = """
253 SELECT data, list_label, field_label FROM (
254
255 SELECT DISTINCT ON (data)
256 data,
257 list_label,
258 field_label
259 FROM ((
260 -- fragment -> vaccine
261 SELECT
262 r_v_v.pk_vaccine
263 AS data,
264 r_v_v.vaccine || ' ('
265 || (SELECT string_agg(l10n_inds.ind_desc::text, ', ') FROM (
266 SELECT unnest(r_v_v.indications)->>'l10n_indication' AS ind_desc
267 ) AS l10n_inds)
268 || ')'
269 AS list_label,
270 r_v_v.vaccine
271 AS field_label
272 FROM
273 ref.v_vaccines r_v_v
274 WHERE
275 r_v_v.vaccine %(fragment_condition)s
276
277 ) union all (
278
279 -- fragment -> localized indication -> vaccines
280 SELECT
281 r_vi4v.pk_vaccine
282 AS data,
283 r_vi4v.vaccine || ' ('
284 || (SELECT string_agg(l10n_inds.ind_desc::text, ', ') FROM (
285 SELECT unnest(r_vi4v.indications)->>'l10n_indication' AS ind_desc
286 ) AS l10n_inds)
287 || ')'
288 AS list_label,
289 r_vi4v.vaccine
290 AS field_label
291 FROM
292 ref.v_indications4vaccine r_vi4v
293 WHERE
294 r_vi4v.l10n_indication %(fragment_condition)s
295
296 ) union all (
297
298 -- fragment -> indication -> vaccines
299 SELECT
300 r_vi4v.pk_vaccine
301 AS data,
302 r_vi4v.vaccine || ' ('
303 || (SELECT string_agg(l10n_inds.ind_desc::text, ', ') FROM (
304 SELECT unnest(r_vi4v.indications)->>'l10n_indication' AS ind_desc
305 ) AS l10n_inds)
306 || ')'
307 AS list_label,
308 r_vi4v.vaccine
309 AS field_label
310 FROM
311 ref.v_indications4vaccine r_vi4v
312 WHERE
313 r_vi4v.indication %(fragment_condition)s
314 )
315 ) AS distinct_total
316
317 ) AS total
318
319 ORDER by list_label
320 LIMIT 25
321 """
322 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
323 mp.setThresholds(1, 2, 3)
324 self.matcher = mp
325
326 self.selection_only = True
327
330
331
332 from Gnumed.wxGladeWidgets import wxgVaccineEAPnl
333
334 -class cVaccineEAPnl(wxgVaccineEAPnl.wxgVaccineEAPnl, gmEditArea.cGenericEditAreaMixin):
335
352
353
355 self._TCTRL_indications.SetValue('')
356 if self.data is None:
357 return
358 self._TCTRL_indications.SetValue('- ' + '\n- '.join([ i['l10n_indication'] for i in self.data['indications'] ]))
359
360
361
362
364
365 has_errors = False
366
367 if self._PRW_drug_product.GetValue().strip() == '':
368 has_errors = True
369 self._PRW_drug_product.display_as_valid(False)
370 else:
371 self._PRW_drug_product.display_as_valid(True)
372
373 atc = self._PRW_atc.GetValue().strip()
374 if (atc == '') or (atc.startswith('J07')):
375 self._PRW_atc.display_as_valid(True)
376 else:
377 if self._PRW_atc.GetData() is None:
378 self._PRW_atc.display_as_valid(True)
379 else:
380 has_errors = True
381 self._PRW_atc.display_as_valid(False)
382
383 val = self._PRW_age_min.GetValue().strip()
384 if val == '':
385 self._PRW_age_min.display_as_valid(True)
386 else:
387 if gmDateTime.str2interval(val) is None:
388 has_errors = True
389 self._PRW_age_min.display_as_valid(False)
390 else:
391 self._PRW_age_min.display_as_valid(True)
392
393 val = self._PRW_age_max.GetValue().strip()
394 if val == '':
395 self._PRW_age_max.display_as_valid(True)
396 else:
397 if gmDateTime.str2interval(val) is None:
398 has_errors = True
399 self._PRW_age_max.display_as_valid(False)
400 else:
401 self._PRW_age_max.display_as_valid(True)
402
403
404
405 if self.mode == 'edit':
406 change_of_product = self.data['pk_drug_product'] != self._PRW_drug_product.GetData()
407 if change_of_product and self.data.is_in_use:
408 do_it = gmGuiHelpers.gm_show_question (
409 aTitle = _('Saving vaccine'),
410 aMessage = _(
411 'This vaccine is already in use:\n'
412 '\n'
413 ' "%s"\n'
414 '\n'
415 'Are you absolutely positively sure that\n'
416 'you really want to edit this vaccine ?\n'
417 '\n'
418 'This will change the vaccine name and/or target\n'
419 'conditions in each patient this vaccine was\n'
420 'used in to document a vaccination with.\n'
421 ) % self._PRW_drug_product.GetValue().strip()
422 )
423 if not do_it:
424 has_errors = True
425 else:
426 if self._PRW_drug_product.GetData() is None:
427
428 if self._PRW_drug_product.GetValue().strip() != '':
429 self.__indications = gmSubstanceMgmtWidgets.manage_substance_doses(vaccine_indications_only = True)
430 if self.__indications is None:
431 has_errors = True
432 else:
433
434 pass
435
436 return (has_errors is False)
437
438
473
474
476
477 drug = self.data.product
478 drug['product'] = self._PRW_drug_product.GetValue().strip()
479 drug['is_fake_product'] = self._CHBOX_fake.GetValue()
480 val = self._PRW_atc.GetData()
481 if val is not None:
482 if val != 'J07':
483 drug['atc'] = val.strip()
484 drug.save()
485
486 self.data['is_live'] = self._CHBOX_live.GetValue()
487 val = self._PRW_age_min.GetValue().strip()
488 if val != '':
489 self.data['min_age'] = gmDateTime.str2interval(val)
490 if val != '':
491 self.data['max_age'] = gmDateTime.str2interval(val)
492 val = self._TCTRL_comment.GetValue().strip()
493 if val != '':
494 self.data['comment'] = val
495 self.data.save()
496
497 return True
498
499
501 self._PRW_drug_product.SetText(value = '', data = None, suppress_smarts = True)
502 self._CHBOX_live.SetValue(False)
503 self._CHBOX_fake.SetValue(False)
504 self._PRW_atc.SetText(value = '', data = None, suppress_smarts = True)
505 self._PRW_age_min.SetText(value = '', data = None, suppress_smarts = True)
506 self._PRW_age_max.SetText(value = '', data = None, suppress_smarts = True)
507 self._TCTRL_comment.SetValue('')
508
509 self.__refresh_indications()
510
511 self._PRW_drug_product.SetFocus()
512
513
515 self._PRW_drug_product.SetText(value = self.data['vaccine'], data = self.data['pk_drug_product'])
516 self._CHBOX_live.SetValue(self.data['is_live'])
517 self._CHBOX_fake.SetValue(self.data['is_fake_vaccine'])
518 self._PRW_atc.SetText(value = self.data['atc_code'], data = self.data['atc_code'])
519 if self.data['min_age'] is None:
520 self._PRW_age_min.SetText(value = '', data = None, suppress_smarts = True)
521 else:
522 self._PRW_age_min.SetText (
523 value = gmDateTime.format_interval(self.data['min_age'], gmDateTime.acc_years),
524 data = self.data['min_age']
525 )
526 if self.data['max_age'] is None:
527 self._PRW_age_max.SetText(value = '', data = None, suppress_smarts = True)
528 else:
529 self._PRW_age_max.SetText (
530 value = gmDateTime.format_interval(self.data['max_age'], gmDateTime.acc_years),
531 data = self.data['max_age']
532 )
533 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
534
535 self.__refresh_indications()
536
537 self._PRW_drug_product.SetFocus()
538
539
541 self._refresh_as_new()
542
543
544
545
557
558 gmCfgWidgets.configure_string_option (
559 message = _(
560 'GNUmed will use this URL to access a website which lets\n'
561 'you report an adverse vaccination reaction (vADR).\n'
562 '\n'
563 'If you set it to a specific address that URL must be\n'
564 'accessible now. If you leave it empty it will fall back\n'
565 'to the URL for reporting other adverse drug reactions.'
566 ),
567 option = 'external.urls.report_vaccine_ADR',
568 bias = 'user',
569 default_value = gmVaccination.URL_vaccine_adr_german_default,
570 validator = is_valid
571 )
572
573
585
586 gmCfgWidgets.configure_string_option (
587 message = _(
588 'GNUmed will use this URL to access a page showing\n'
589 'vaccination schedules.\n'
590 '\n'
591 'You can leave this empty but to set it to a specific\n'
592 'address the URL must be accessible now.'
593 ),
594 option = 'external.urls.vaccination_plans',
595 bias = 'user',
596 default_value = gmVaccination.URL_vaccination_plan,
597 validator = is_valid
598 )
599
600
627
628
642
643
664
665
666 def print_vaccs(vaccination=None):
667 print_vaccinations(parent = parent)
668 return False
669
670
671 def add_recall(vaccination=None):
672 if vaccination is None:
673 subject = _('vaccination recall')
674 else:
675 subject = _('vaccination recall (%s)') % vaccination['vaccine']
676
677 recall = gmProviderInbox.create_inbox_message (
678 message_type = _('Vaccination'),
679 subject = subject,
680 patient = pat.ID,
681 staff = None
682 )
683
684 if vaccination is not None:
685 recall['data'] = _('Existing vaccination:\n\n%s') % '\n'.join(vaccination.format(
686 with_indications = True,
687 with_comment = True,
688 with_reaction = False,
689 date_format = '%Y %b %d'
690 ))
691 recall.save()
692
693 from Gnumed.wxpython import gmProviderInboxWidgets
694 gmProviderInboxWidgets.edit_inbox_message (
695 parent = parent,
696 message = recall,
697 single_entry = False
698 )
699
700 return False
701
702
703 def get_tooltip(vaccination):
704 if vaccination is None:
705 return None
706 return '\n'.join(vaccination.format (
707 with_indications = True,
708 with_comment = True,
709 with_reaction = True,
710 date_format = '%Y %b %d'
711 ))
712
713
714 def edit(vaccination=None):
715 return edit_vaccination(parent = parent, vaccination = vaccination, single_entry = (vaccination is not None))
716
717
718 def delete(vaccination=None):
719 gmVaccination.delete_vaccination(vaccination = vaccination['pk_vaccination'])
720 return True
721
722
723 def refresh(lctrl):
724
725 items = []
726 data = []
727 if latest_only:
728 latest_vaccs = emr.get_latest_vaccinations()
729 for indication in sorted(latest_vaccs):
730 no_of_shots4ind, latest_vacc4ind = latest_vaccs[indication]
731 items.append ([
732 indication,
733 _('%s (latest of %s: %s ago)') % (
734 gmDateTime.pydt_strftime(latest_vacc4ind['date_given'], format = '%Y %b'),
735 no_of_shots4ind,
736 gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - latest_vacc4ind['date_given'])
737 ),
738 latest_vacc4ind['vaccine'],
739 latest_vacc4ind['batch_no'],
740 gmTools.coalesce(latest_vacc4ind['site'], ''),
741 gmTools.coalesce(latest_vacc4ind['reaction'], ''),
742 gmTools.coalesce(latest_vacc4ind['comment'], '')
743 ])
744 data.append(latest_vacc4ind)
745 else:
746 shots = emr.get_vaccinations(order_by = 'date_given DESC, pk_vaccination')
747 if expand_indications:
748 shots_by_ind = {}
749 for shot in shots:
750 for ind in shot['indications']:
751 try:
752 shots_by_ind[ind['l10n_indication']].append(shot)
753 except KeyError:
754 shots_by_ind[ind['l10n_indication']] = [shot]
755 for ind in sorted(shots_by_ind):
756 idx = len(shots_by_ind[ind])
757 for shot in shots_by_ind[ind]:
758 items.append ([
759 '%s (#%s)' % (ind, idx),
760 _('%s (%s ago)') % (
761 gmDateTime.pydt_strftime(shot['date_given'], '%Y %b %d'),
762 gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - shot['date_given'])
763 ),
764 shot['vaccine'],
765 shot['batch_no'],
766 gmTools.coalesce(shot['site'], ''),
767 gmTools.coalesce(shot['reaction'], ''),
768 gmTools.coalesce(shot['comment'], '')
769 ])
770 idx -= 1
771 data.append(shot)
772 else:
773 items = [ [
774 gmDateTime.pydt_strftime(s['date_given'], '%Y %b %d'),
775 s['vaccine'],
776 ', '.join([ i['l10n_indication'] for i in s['indications'] ]),
777 s['batch_no'],
778 gmTools.coalesce(s['site'], ''),
779 gmTools.coalesce(s['reaction'], ''),
780 gmTools.coalesce(s['comment'], '')
781 ] for s in shots ]
782 data = shots
783
784 lctrl.set_string_items(items)
785 lctrl.set_data(data)
786
787
788 if latest_only:
789 msg = _('Most recent vaccination for each indication.\n')
790 cols = [ _('Indication'), _('Date'), _('Vaccine'), _('Batch'), _('Site'), _('Reaction'), _('Comment') ]
791 else:
792 if expand_indications:
793 msg = _('Complete vaccination history (per indication).\n')
794 cols = [ _('Indication'), _('Date'), _('Vaccine'), _('Batch'), _('Site'), _('Reaction'), _('Comment') ]
795 else:
796 msg = _('Complete vaccination history (by shot).\n')
797 cols = [ _('Date'), _('Vaccine'), _('Intended to protect from'), _('Batch'), _('Site'), _('Reaction'), _('Comment') ]
798
799 gmListWidgets.get_choices_from_list (
800 parent = parent,
801 msg = msg,
802 caption = _('Showing vaccinations.'),
803 columns = cols,
804 single_selection = True,
805 refresh_callback = refresh,
806 new_callback = edit,
807 edit_callback = edit,
808 delete_callback = delete,
809 list_tooltip_callback = get_tooltip,
810 left_extra_button = (_('Print'), _('Print vaccinations or recalls.'), print_vaccs),
811 middle_extra_button = (_('Recall'), _('Add a recall for a vaccination'), add_recall),
812 right_extra_button = (_('Vx schedules'), _('Open a browser showing vaccination schedules.'), browse2schedules)
813 )
814
815
816 from Gnumed.wxGladeWidgets import wxgVaccinationEAPnl
817
818 -class cVaccinationEAPnl(wxgVaccinationEAPnl.wxgVaccinationEAPnl, gmEditArea.cGenericEditAreaMixin):
819 """
820 - warn on apparent duplicates
821 - ask if "missing" (= previous, non-recorded) vaccinations
822 should be estimated and saved (add note "auto-generated")
823
824 Batch No (http://www.fao.org/docrep/003/v9952E12.htm)
825 """
843
844
850
851
853
854 vaccine = self._PRW_vaccine.GetData(as_instance=True)
855
856 if self.mode == 'edit':
857 if vaccine is None:
858 self._PRW_batch.unset_context(context = 'pk_vaccine')
859 else:
860 self._PRW_batch.set_context(context = 'pk_vaccine', val = vaccine['pk_vaccine'])
861
862 else:
863 if vaccine is None:
864 self._PRW_batch.unset_context(context = 'pk_vaccine')
865 else:
866 self._PRW_batch.set_context(context = 'pk_vaccine', val = vaccine['pk_vaccine'])
867
868 self.__refresh_indications()
869
870
872 if self._PRW_reaction.GetValue().strip() == '':
873 self._BTN_report.Enable(False)
874 else:
875 self._BTN_report.Enable(True)
876
877
879 self._TCTRL_indications.SetValue('')
880 vaccine = self._PRW_vaccine.GetData(as_instance = True)
881 if vaccine is None:
882 return
883 lines = []
884 emr = gmPerson.gmCurrentPatient().emr
885 latest_vaccs = emr.get_latest_vaccinations (
886 atc_indications = [ i['atc_indication'] for i in vaccine['indications'] ]
887 )
888 for l10n_ind in [ i['l10n_indication'] for i in vaccine['indications'] ]:
889 try:
890 no_of_shots4ind, latest_vacc4ind = latest_vaccs[l10n_ind]
891 ago = gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - latest_vacc4ind['date_given'])
892 lines.append(_('%s (most recent shot of %s: %s ago)') % (l10n_ind, no_of_shots4ind, ago))
893 except KeyError:
894 lines.append(_('%s (no previous vaccination recorded)') % l10n_ind)
895
896 self._TCTRL_indications.SetValue(_('Protects against:\n ') + '\n '.join(lines))
897
898
899
900
902
903 has_errors = False
904
905 if not self._PRW_date_given.is_valid_timestamp(empty_is_valid = False):
906 has_errors = True
907
908 vaccine = self._PRW_vaccine.GetData(as_instance = True)
909 if vaccine is None:
910 has_errors = True
911 self._PRW_vaccine.display_as_valid(False)
912 else:
913 self._PRW_vaccine.display_as_valid(True)
914
915 if self._PRW_batch.GetValue().strip() == '':
916 has_errors = True
917 self._PRW_batch.display_as_valid(False)
918 else:
919 self._PRW_batch.display_as_valid(True)
920
921 if self._PRW_episode.GetValue().strip() == '':
922 self._PRW_episode.SetText(value = _('prevention'))
923
924 return (has_errors is False)
925
926
938
939
964
965
967
968 if self._CHBOX_anamnestic.GetValue() is True:
969 self.data['soap_cat'] = 's'
970 else:
971 self.data['soap_cat'] = 'p'
972
973 self.data['date_given'] = self._PRW_date_given.GetData()
974 self.data['pk_vaccine'] = self._PRW_vaccine.GetData()
975 self.data['batch_no'] = self._PRW_batch.GetValue().strip()
976 self.data['pk_episode'] = self._PRW_episode.GetData(can_create = True, is_open = False)
977 self.data['site'] = self._PRW_site.GetValue().strip()
978 self.data['pk_provider'] = self._PRW_provider.GetData()
979 self.data['reaction'] = self._PRW_reaction.GetValue().strip()
980 self.data['comment'] = self._TCTRL_comment.GetValue().strip()
981
982 self.data.save()
983
984 return True
985
986
1003
1004
1027
1028
1047
1048
1049
1050
1066
1067
1070
1071
1072
1073
1074
1075
1077
1079 wx.Panel.__init__(self, parent, id, wx.DefaultPosition, wx.DefaultSize, wx.RAISED_BORDER)
1080 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1081 self.__pat = gmPerson.gmCurrentPatient()
1082
1083 self.ID_VaccinatedIndicationsList = wx.NewId()
1084 self.ID_VaccinationsPerRegimeList = wx.NewId()
1085 self.ID_MissingShots = wx.NewId()
1086 self.ID_ActiveSchedules = wx.NewId()
1087 self.__do_layout()
1088 self.__register_interests()
1089 self.__reset_ui_content()
1090
1092
1093
1094
1095 pnl_UpperCaption = gmTerryGuiParts.cHeadingCaption(self, -1, _(" IMMUNISATIONS "))
1096 self.editarea = cVaccinationEditArea(self, -1, wx.DefaultPosition, wx.DefaultSize, wx.NO_BORDER)
1097
1098
1099
1100
1101
1102 indications_heading = gmTerryGuiParts.cDividerCaption(self, -1, _("Indications"))
1103 vaccinations_heading = gmTerryGuiParts.cDividerCaption(self, -1, _("Vaccinations"))
1104 schedules_heading = gmTerryGuiParts.cDividerCaption(self, -1, _("Active Schedules"))
1105 szr_MiddleCap = wx.BoxSizer(wx.HORIZONTAL)
1106 szr_MiddleCap.Add(indications_heading, 4, wx.EXPAND)
1107 szr_MiddleCap.Add(vaccinations_heading, 6, wx.EXPAND)
1108 szr_MiddleCap.Add(schedules_heading, 10, wx.EXPAND)
1109
1110
1111 self.LBOX_vaccinated_indications = wx.ListBox(
1112 parent = self,
1113 id = self.ID_VaccinatedIndicationsList,
1114 choices = [],
1115 style = wx.LB_HSCROLL | wx.LB_NEEDED_SB | wx.SUNKEN_BORDER
1116 )
1117 self.LBOX_vaccinated_indications.SetFont(wx.Font(12,wx.SWISS, wx.NORMAL, wx.NORMAL, False, ''))
1118
1119
1120
1121 self.LBOX_given_shots = wx.ListBox(
1122 parent = self,
1123 id = self.ID_VaccinationsPerRegimeList,
1124 choices = [],
1125 style = wx.LB_HSCROLL | wx.LB_NEEDED_SB | wx.SUNKEN_BORDER
1126 )
1127 self.LBOX_given_shots.SetFont(wx.Font(12,wx.SWISS, wx.NORMAL, wx.NORMAL, False, ''))
1128
1129 self.LBOX_active_schedules = wx.ListBox (
1130 parent = self,
1131 id = self.ID_ActiveSchedules,
1132 choices = [],
1133 style = wx.LB_HSCROLL | wx.LB_NEEDED_SB | wx.SUNKEN_BORDER
1134 )
1135 self.LBOX_active_schedules.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, False, ''))
1136
1137 szr_MiddleLists = wx.BoxSizer(wx.HORIZONTAL)
1138 szr_MiddleLists.Add(self.LBOX_vaccinated_indications, 4, wx.EXPAND)
1139 szr_MiddleLists.Add(self.LBOX_given_shots, 6, wx.EXPAND)
1140 szr_MiddleLists.Add(self.LBOX_active_schedules, 10, wx.EXPAND)
1141
1142
1143
1144
1145 missing_heading = gmTerryGuiParts.cDividerCaption(self, -1, _("Missing Immunisations"))
1146 szr_BottomCap = wx.BoxSizer(wx.HORIZONTAL)
1147 szr_BottomCap.Add(missing_heading, 1, wx.EXPAND)
1148
1149 self.LBOX_missing_shots = wx.ListBox (
1150 parent = self,
1151 id = self.ID_MissingShots,
1152 choices = [],
1153 style = wx.LB_HSCROLL | wx.LB_NEEDED_SB | wx.SUNKEN_BORDER
1154 )
1155 self.LBOX_missing_shots.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, False, ''))
1156
1157 szr_BottomLists = wx.BoxSizer(wx.HORIZONTAL)
1158 szr_BottomLists.Add(self.LBOX_missing_shots, 1, wx.EXPAND)
1159
1160
1161 pnl_AlertCaption = gmTerryGuiParts.cAlertCaption(self, -1, _(' Alerts '))
1162
1163
1164
1165
1166 self.mainsizer = wx.BoxSizer(wx.VERTICAL)
1167 self.mainsizer.Add(pnl_UpperCaption, 0, wx.EXPAND)
1168 self.mainsizer.Add(self.editarea, 6, wx.EXPAND)
1169 self.mainsizer.Add(szr_MiddleCap, 0, wx.EXPAND)
1170 self.mainsizer.Add(szr_MiddleLists, 4, wx.EXPAND)
1171 self.mainsizer.Add(szr_BottomCap, 0, wx.EXPAND)
1172 self.mainsizer.Add(szr_BottomLists, 4, wx.EXPAND)
1173 self.mainsizer.Add(pnl_AlertCaption, 0, wx.EXPAND)
1174
1175 self.SetAutoLayout(True)
1176 self.SetSizer(self.mainsizer)
1177 self.mainsizer.Fit(self)
1178
1180
1181 wx.EVT_SIZE(self, self.OnSize)
1182 wx.EVT_LISTBOX(self, self.ID_VaccinatedIndicationsList, self._on_vaccinated_indication_selected)
1183 wx.EVT_LISTBOX_DCLICK(self, self.ID_VaccinationsPerRegimeList, self._on_given_shot_selected)
1184 wx.EVT_LISTBOX_DCLICK(self, self.ID_MissingShots, self._on_missing_shot_selected)
1185
1186
1187
1188 gmDispatcher.connect(signal= 'post_patient_selection', receiver=self._schedule_data_reget)
1189 gmDispatcher.connect(signal= 'vaccinations_updated', receiver=self._schedule_data_reget)
1190
1191
1192
1194 w, h = event.GetSize()
1195 self.mainsizer.SetDimension (0, 0, w, h)
1196
1201
1204
1206 """Update right hand middle list to show vaccinations given for selected indication."""
1207 ind_list = event.GetEventObject()
1208 selected_item = ind_list.GetSelection()
1209 ind = ind_list.GetClientData(selected_item)
1210
1211 self.LBOX_given_shots.Set([])
1212 emr = self.__pat.emr
1213 shots = emr.get_vaccinations(indications = [ind])
1214
1215 for shot in shots:
1216 if shot['is_booster']:
1217 marker = 'B'
1218 else:
1219 marker = '#%s' % shot['seq_no']
1220 label = '%s - %s: %s' % (marker, shot['date'].strftime('%m/%Y'), shot['vaccine'])
1221 self.LBOX_given_shots.Append(label, shot)
1222
1224
1225 self.editarea.set_data()
1226
1227 self.LBOX_vaccinated_indications.Clear()
1228 self.LBOX_given_shots.Clear()
1229 self.LBOX_active_schedules.Clear()
1230 self.LBOX_missing_shots.Clear()
1231
1233
1234 self.LBOX_vaccinated_indications.Clear()
1235 self.LBOX_given_shots.Clear()
1236 self.LBOX_active_schedules.Clear()
1237 self.LBOX_missing_shots.Clear()
1238
1239 emr = self.__pat.emr
1240
1241 t1 = time.time()
1242
1243
1244
1245 status, indications = emr.get_vaccinated_indications()
1246
1247
1248
1249 for indication in indications:
1250 self.LBOX_vaccinated_indications.Append(indication[1], indication[0])
1251
1252
1253 print("vaccinated indications took", time.time()-t1, "seconds")
1254
1255 t1 = time.time()
1256
1257 scheds = emr.get_scheduled_vaccination_regimes()
1258 if scheds is None:
1259 label = _('ERROR: cannot retrieve active vaccination schedules')
1260 self.LBOX_active_schedules.Append(label)
1261 elif len(scheds) == 0:
1262 label = _('no active vaccination schedules')
1263 self.LBOX_active_schedules.Append(label)
1264 else:
1265 for sched in scheds:
1266 label = _('%s for %s (%s shots): %s') % (sched['regime'], sched['l10n_indication'], sched['shots'], sched['comment'])
1267 self.LBOX_active_schedules.Append(label)
1268 print("active schedules took", time.time()-t1, "seconds")
1269
1270 t1 = time.time()
1271
1272 missing_shots = emr.get_missing_vaccinations()
1273 print("getting missing shots took", time.time()-t1, "seconds")
1274 if missing_shots is None:
1275 label = _('ERROR: cannot retrieve due/overdue vaccinations')
1276 self.LBOX_missing_shots.Append(label, None)
1277 return True
1278
1279 due_template = _('%.0d weeks left: shot %s for %s in %s, due %s (%s)')
1280 overdue_template = _('overdue %.0dyrs %.0dwks: shot %s for %s in schedule "%s" (%s)')
1281 for shot in missing_shots['due']:
1282 if shot['overdue']:
1283 years, days_left = divmod(shot['amount_overdue'].days, 364.25)
1284 weeks = days_left / 7
1285
1286 label = overdue_template % (
1287 years,
1288 weeks,
1289 shot['seq_no'],
1290 shot['l10n_indication'],
1291 shot['regime'],
1292 shot['vacc_comment']
1293 )
1294 self.LBOX_missing_shots.Append(label, shot)
1295 else:
1296
1297 label = due_template % (
1298 shot['time_left'].days / 7,
1299 shot['seq_no'],
1300 shot['indication'],
1301 shot['regime'],
1302 shot['latest_due'].strftime('%m/%Y'),
1303 shot['vacc_comment']
1304 )
1305 self.LBOX_missing_shots.Append(label, shot)
1306
1307 lbl_template = _('due now: booster for %s in schedule "%s" (%s)')
1308 for shot in missing_shots['boosters']:
1309
1310 label = lbl_template % (
1311 shot['l10n_indication'],
1312 shot['regime'],
1313 shot['vacc_comment']
1314 )
1315 self.LBOX_missing_shots.Append(label, shot)
1316 print("displaying missing shots took", time.time()-t1, "seconds")
1317
1318 return True
1319
1320 - def _on_post_patient_selection(self, **kwargs):
1322
1323
1324
1325
1326
1327
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339 if __name__ == "__main__":
1340
1341 if len(sys.argv) < 2:
1342 sys.exit()
1343
1344 if sys.argv[1] != 'test':
1345 sys.exit()
1346
1347 app = wx.PyWidgetTester(size = (600, 600))
1348 app.SetWidget(cXxxPhraseWheel, -1)
1349 app.MainLoop()
1350