1 """GnuMed immunisation/vaccination widgets.
2
3 Modelled after Richard Terry's design document.
4
5 copyright: authors
6 """
7
8
9
10 __version__ = "$Revision: 1.36 $"
11 __author__ = "R.Terry, S.J.Tan, K.Hilbert"
12 __license__ = "GPL (details at http://www.gnu.org)"
13
14 import sys, time
15
16
17 import wx
18 import mx.DateTime as mxDT
19
20
21 from Gnumed.wxpython import gmEditArea, gmPhraseWheel, gmTerryGuiParts, gmRegetMixin, gmGuiHelpers
22 from Gnumed.business import gmPerson, gmVaccination
23 from Gnumed.pycommon import gmDispatcher, gmExceptions, gmMatchProvider
24
25 _log = gmLog.gmDefLog
26 _log.Log(gmLog.lInfo, __version__)
27
29 """
30 - warn on apparent duplicates
31 - ask if "missing" (= previous, non-recorded) vaccinations
32 should be estimated and saved (add note "auto-generated")
33 """
34 - def __init__(self, parent, id, pos, size, style, data_sink=None):
37
39
40
41
42
43
44
45
46
47
48
49
50
51
52 query = """
53 select
54 pk,
55 trade_name
56 from
57 vaccine
58 where
59 short_name || ' ' || trade_name %(fragment_condition)s
60 limit 25"""
61 mp = gmMatchProvider.cMatchProvider_SQL2([query])
62 mp.setThresholds(aWord=2, aSubstring=4)
63 self.fld_vaccine = gmPhraseWheel.cPhraseWheel(
64 parent = parent
65 , id = -1
66 , style = wx.SIMPLE_BORDER
67 )
68 self.fld_vaccine.matcher = mp
69 gmEditArea._decorate_editarea_field(self.fld_vaccine)
70 self._add_field(
71 line = 1,
72 pos = 1,
73 widget = self.fld_vaccine,
74 weight = 3
75 )
76
77
78 self.fld_date_given = gmEditArea.cEditAreaField(parent)
79 self._add_field(
80 line = 2,
81 pos = 1,
82 widget = self.fld_date_given,
83 weight = 2
84 )
85
86
87 self.fld_batch_no = gmEditArea.cEditAreaField(parent)
88 self._add_field(
89 line = 3,
90 pos = 1,
91 widget = self.fld_batch_no,
92 weight = 1
93 )
94
95
96 query = """
97 select distinct on (tmp.site)
98 tmp.id, tmp.site
99 from (
100 select id, site
101 from vaccination
102 group by id, site
103 order by count(site)
104 ) as tmp
105 where
106 tmp.site %(fragment_condition)s
107 limit 10"""
108 mp = gmMatchProvider.cMatchProvider_SQL2([query])
109 mp.setThresholds(aWord=1, aSubstring=3)
110 self.fld_site_given = gmPhraseWheel.cPhraseWheel(
111 parent = parent
112 , id = -1
113 , style = wx.SIMPLE_BORDER
114 )
115 self.fld_site_given.matcher = mp
116 gmEditArea._decorate_editarea_field(self.fld_site_given)
117 self._add_field(
118 line = 4,
119 pos = 1,
120 widget = self.fld_site_given,
121 weight = 1
122 )
123
124
125 query = """
126 select distinct on (narrative)
127 id, narrative
128 from
129 vaccination
130 where
131 narrative %(fragment_condition)s
132 limit 30"""
133 mp = gmMatchProvider.cMatchProvider_SQL2([query])
134 mp.setThresholds(aWord=3, aSubstring=5)
135 self.fld_progress_note = gmPhraseWheel.cPhraseWheel(
136 parent = parent
137 , id = -1
138 , style = wx.SIMPLE_BORDER
139 )
140 self.fld_progress_note = mp
141 gmEditArea._decorate_editarea_field(self.fld_progress_note)
142 self._add_field(
143 line = 5,
144 pos = 1,
145 widget = self.fld_progress_note,
146 weight = 1
147 )
148 return 1
149
151 self._add_prompt(line = 1, label = _("Vaccine"))
152 self._add_prompt(line = 2, label = _("Date given"))
153 self._add_prompt(line = 3, label = _("Serial #"))
154 self._add_prompt(line = 4, label = _("Site injected"))
155 self._add_prompt(line = 5, label = _("Progress Note"))
156
157 - def _save_new_entry(self, episode):
158
159 if self.__data_sink is None:
160
161 emr = self._patient.get_emr()
162
163 successfull, data = emr.add_vaccination(vaccine=self.fld_vaccine.GetValue(), episode=episode)
164 if not successfull:
165 gmDispatcher.send(signal = 'statustext', msg =_('Cannot save vaccination: %s') % data)
166 return False
167
168 data['pk_provider'] = gmPerson.gmCurrentProvider()['pk_staff']
169 data['date'] = self.fld_date_given.GetValue()
170 data['narrative'] = self.fld_progress_note.GetValue()
171 data['site'] = self.fld_site_given.GetValue()
172 data['batch_no'] = self.fld_batch_no.GetValue()
173 successful, err = data.save_payload()
174 if not successful:
175 gmDispatcher.send(signal = 'statustext', msg =_('Cannot save new vaccination: %s') % err)
176 return False
177 gmDispatcher.send(signal = 'statustext', msg =_('Vaccination saved.'))
178 self.data = data
179 return True
180 else:
181
182 data = {
183 'vaccine': self.fld_vaccine.GetValue(),
184 'pk_provider': gmPerson.gmCurrentProvider()['pk_staff'],
185 'date': self.fld_date_given.GetValue(),
186 'narrative': self.fld_progress_note.GetValue(),
187 'site': self.fld_site_given.GetValue(),
188 'batch_no': self.fld_batch_no.GetValue()
189 }
190
191 successful = self.__data_sink (
192 popup_type = 'vaccination',
193 data = data,
194 desc = _('shot: %s, %s, %s') % (data['date'], data['vaccine'], data['site'])
195 )
196 if not successful:
197 gmDispatcher.send(signal = 'statustext', msg =_('Cannot queue new vaccination.'))
198 return False
199 gmDispatcher.send(signal = 'statustext', msg =_('Vaccination queued for saving.'))
200 return True
201
203 """Update vaccination object and persist to backend.
204 """
205 self.data['vaccine'] = self.fld_vaccine.GetValue()
206 self.data['batch_no'] = self.fld_batch_no.GetValue()
207 self.data['date'] = self.fld_date_given.GetValue()
208 self.data['site'] = self.fld_site_given.GetValue()
209 self.data['narrative'] = self.fld_progress_note.GetValue()
210 successfull, data = self.data.save_payload()
211 if not successfull:
212 gmDispatcher.send(signal = 'statustext', msg =_('Cannot update vaccination: %s') % err)
213 return False
214 gmDispatcher.send(signal = 'statustext', msg =_('Vaccination updated.'))
215 return True
216
218 if self.data is None:
219 return self._save_new_entry(episode=episode)
220 else:
221 return self._save_modified_entry()
222
224 """Set edit area fields with vaccination object data.
225
226 - set defaults if no object is passed in, this will
227 result in a new object being created upon saving
228 """
229
230 if aVacc is None:
231 self.data = None
232 self.fld_vaccine.SetValue('')
233 self.fld_batch_no.SetValue('')
234 self.fld_date_given.SetValue((time.strftime('%Y-%m-%d', time.localtime())))
235 self.fld_site_given.SetValue(_('left/right deltoid'))
236 self.fld_progress_note.SetValue('')
237 return True
238
239
240 if isinstance(aVacc, gmVaccination.cVaccination):
241 self.data = aVacc
242 self.fld_vaccine.SetValue(aVacc['vaccine'])
243 self.fld_batch_no.SetValue(aVacc['batch_no'])
244 self.fld_date_given.SetValue(aVacc['date'].strftime('%Y-%m-%d'))
245 self.fld_site_given.SetValue(aVacc['site'])
246 self.fld_progress_note.SetValue(aVacc['narrative'])
247 return True
248
249
250 if isinstance(aVacc, gmVaccination.cMissingVaccination):
251 self.data = None
252
253 self.fld_vaccine.SetValue('')
254 self.fld_batch_no.SetValue('')
255 self.fld_date_given.SetValue((time.strftime('%Y-%m-%d', time.localtime())))
256
257 self.fld_site_given.SetValue(_('left/right deltoid'))
258 if aVacc['overdue']:
259 self.fld_progress_note.SetValue(_('was due: %s, delayed because:') % aVacc['latest_due'].strftime('%x'))
260 else:
261 self.fld_progress_note.SetValue('')
262 return True
263
264
265 if isinstance(aVacc, gmVaccination.cMissingBooster):
266 self.data = None
267 self.fld_vaccine.SetValue('')
268 self.fld_batch_no.SetValue('')
269 self.fld_date_given.SetValue((time.strftime('%Y-%m-%d', time.localtime())))
270
271 self.fld_site_given.SetValue(_('left/right deltoid'))
272 if aVacc['overdue']:
273 self.fld_progress_note.SetValue(_('booster: was due: %s, delayed because:') % aVacc['latest_due'].strftime('%Y-%m-%d'))
274 else:
275 self.fld_progress_note.SetValue(_('booster'))
276 return True
277
278 _log.Log(gmLog.lErr, 'do not know how to handle [%s:%s]' % (type(aVacc), str(aVacc)))
279 return False
280
282
284 wx.Panel.__init__(self, parent, id, wx.DefaultPosition, wx.DefaultSize, wx.RAISED_BORDER)
285 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
286 self.__pat = gmPerson.gmCurrentPatient()
287
288 self.ID_VaccinatedIndicationsList = wx.NewId()
289 self.ID_VaccinationsPerRegimeList = wx.NewId()
290 self.ID_MissingShots = wx.NewId()
291 self.ID_ActiveSchedules = wx.NewId()
292 self.__do_layout()
293 self.__register_interests()
294 self.__reset_ui_content()
295
297
298
299
300 pnl_UpperCaption = gmTerryGuiParts.cHeadingCaption(self, -1, _(" IMMUNISATIONS "))
301 self.editarea = cVaccinationEditArea(self, -1, wx.DefaultPosition, wx.DefaultSize, wx.NO_BORDER)
302
303
304
305
306
307 indications_heading = gmTerryGuiParts.cDividerCaption(self, -1, _("Indications"))
308 vaccinations_heading = gmTerryGuiParts.cDividerCaption(self, -1, _("Vaccinations"))
309 schedules_heading = gmTerryGuiParts.cDividerCaption(self, -1, _("Active Schedules"))
310 szr_MiddleCap = wx.BoxSizer(wx.HORIZONTAL)
311 szr_MiddleCap.Add(indications_heading, 4, wx.EXPAND)
312 szr_MiddleCap.Add(vaccinations_heading, 6, wx.EXPAND)
313 szr_MiddleCap.Add(schedules_heading, 10, wx.EXPAND)
314
315
316 self.LBOX_vaccinated_indications = wx.ListBox(
317 parent = self,
318 id = self.ID_VaccinatedIndicationsList,
319 choices = [],
320 style = wx.LB_HSCROLL | wx.LB_NEEDED_SB | wx.SUNKEN_BORDER
321 )
322 self.LBOX_vaccinated_indications.SetFont(wx.Font(12,wx.SWISS, wx.NORMAL, wx.NORMAL, False, ''))
323
324
325
326 self.LBOX_given_shots = wx.ListBox(
327 parent = self,
328 id = self.ID_VaccinationsPerRegimeList,
329 choices = [],
330 style = wx.LB_HSCROLL | wx.LB_NEEDED_SB | wx.SUNKEN_BORDER
331 )
332 self.LBOX_given_shots.SetFont(wx.Font(12,wx.SWISS, wx.NORMAL, wx.NORMAL, False, ''))
333
334 self.LBOX_active_schedules = wx.ListBox (
335 parent = self,
336 id = self.ID_ActiveSchedules,
337 choices = [],
338 style = wx.LB_HSCROLL | wx.LB_NEEDED_SB | wx.SUNKEN_BORDER
339 )
340 self.LBOX_active_schedules.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, False, ''))
341
342 szr_MiddleLists = wx.BoxSizer(wx.HORIZONTAL)
343 szr_MiddleLists.Add(self.LBOX_vaccinated_indications, 4, wx.EXPAND)
344 szr_MiddleLists.Add(self.LBOX_given_shots, 6, wx.EXPAND)
345 szr_MiddleLists.Add(self.LBOX_active_schedules, 10, wx.EXPAND)
346
347
348
349
350 missing_heading = gmTerryGuiParts.cDividerCaption(self, -1, _("Missing Immunisations"))
351 szr_BottomCap = wx.BoxSizer(wx.HORIZONTAL)
352 szr_BottomCap.Add(missing_heading, 1, wx.EXPAND)
353
354 self.LBOX_missing_shots = wx.ListBox (
355 parent = self,
356 id = self.ID_MissingShots,
357 choices = [],
358 style = wx.LB_HSCROLL | wx.LB_NEEDED_SB | wx.SUNKEN_BORDER
359 )
360 self.LBOX_missing_shots.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, False, ''))
361
362 szr_BottomLists = wx.BoxSizer(wx.HORIZONTAL)
363 szr_BottomLists.Add(self.LBOX_missing_shots, 1, wx.EXPAND)
364
365
366 pnl_AlertCaption = gmTerryGuiParts.cAlertCaption(self, -1, _(' Alerts '))
367
368
369
370
371 self.mainsizer = wx.BoxSizer(wx.VERTICAL)
372 self.mainsizer.Add(pnl_UpperCaption, 0, wx.EXPAND)
373 self.mainsizer.Add(self.editarea, 6, wx.EXPAND)
374 self.mainsizer.Add(szr_MiddleCap, 0, wx.EXPAND)
375 self.mainsizer.Add(szr_MiddleLists, 4, wx.EXPAND)
376 self.mainsizer.Add(szr_BottomCap, 0, wx.EXPAND)
377 self.mainsizer.Add(szr_BottomLists, 4, wx.EXPAND)
378 self.mainsizer.Add(pnl_AlertCaption, 0, wx.EXPAND)
379
380 self.SetAutoLayout(True)
381 self.SetSizer(self.mainsizer)
382 self.mainsizer.Fit(self)
383
385
386 wx.EVT_SIZE(self, self.OnSize)
387 wx.EVT_LISTBOX(self, self.ID_VaccinatedIndicationsList, self._on_vaccinated_indication_selected)
388 wx.EVT_LISTBOX_DCLICK(self, self.ID_VaccinationsPerRegimeList, self._on_given_shot_selected)
389 wx.EVT_LISTBOX_DCLICK(self, self.ID_MissingShots, self._on_missing_shot_selected)
390
391
392
393 gmDispatcher.connect(signal= u'post_patient_selection', receiver=self._schedule_data_reget)
394 gmDispatcher.connect(signal= u'vaccinations_updated', receiver=self._schedule_data_reget)
395
396
397
399 w, h = event.GetSize()
400 self.mainsizer.SetDimension (0, 0, w, h)
401
403 """Paste previously given shot into edit area.
404 """
405 self.editarea.set_data(aVacc=event.GetClientData())
406
408 self.editarea.set_data(aVacc = event.GetClientData())
409
411 """Update right hand middle list to show vaccinations given for selected indication."""
412 ind_list = event.GetEventObject()
413 selected_item = ind_list.GetSelection()
414 ind = ind_list.GetClientData(selected_item)
415
416 self.LBOX_given_shots.Set([])
417 emr = self.__pat.get_emr()
418 shots = emr.get_vaccinations(indications = [ind])
419
420 for shot in shots:
421 if shot['is_booster']:
422 marker = 'B'
423 else:
424 marker = '#%s' % shot['seq_no']
425 label = '%s - %s: %s' % (marker, shot['date'].strftime('%m/%Y'), shot['vaccine'])
426 self.LBOX_given_shots.Append(label, shot)
427
429
430 self.editarea.set_data()
431
432 self.LBOX_vaccinated_indications.Clear()
433 self.LBOX_given_shots.Clear()
434 self.LBOX_active_schedules.Clear()
435 self.LBOX_missing_shots.Clear()
436
438
439 self.LBOX_vaccinated_indications.Clear()
440 self.LBOX_given_shots.Clear()
441 self.LBOX_active_schedules.Clear()
442 self.LBOX_missing_shots.Clear()
443
444 emr = self.__pat.get_emr()
445
446 t1 = time.time()
447
448
449
450 status, indications = emr.get_vaccinated_indications()
451
452
453
454 for indication in indications:
455 self.LBOX_vaccinated_indications.Append(indication[1], indication[0])
456
457
458 print "vaccinated indications took", time.time()-t1, "seconds"
459
460 t1 = time.time()
461
462 scheds = emr.get_scheduled_vaccination_regimes()
463 if scheds is None:
464 label = _('ERROR: cannot retrieve active vaccination schedules')
465 self.LBOX_active_schedules.Append(label)
466 elif len(scheds) == 0:
467 label = _('no active vaccination schedules')
468 self.LBOX_active_schedules.Append(label)
469 else:
470 for sched in scheds:
471 label = _('%s for %s (%s shots): %s') % (sched['regime'], sched['l10n_indication'], sched['shots'], sched['comment'])
472 self.LBOX_active_schedules.Append(label)
473 print "active schedules took", time.time()-t1, "seconds"
474
475 t1 = time.time()
476
477 missing_shots = emr.get_missing_vaccinations()
478 print "getting missing shots took", time.time()-t1, "seconds"
479 if missing_shots is None:
480 label = _('ERROR: cannot retrieve due/overdue vaccinations')
481 self.LBOX_missing_shots.Append(label, None)
482 return True
483
484 due_template = _('%.0d weeks left: shot %s for %s in %s, due %s (%s)')
485 overdue_template = _('overdue %.0dyrs %.0dwks: shot %s for %s in schedule "%s" (%s)')
486 for shot in missing_shots['due']:
487 if shot['overdue']:
488 years, days_left = divmod(shot['amount_overdue'].days, 364.25)
489 weeks = days_left / 7
490
491 label = overdue_template % (
492 years,
493 weeks,
494 shot['seq_no'],
495 shot['l10n_indication'],
496 shot['regime'],
497 shot['vacc_comment']
498 )
499 self.LBOX_missing_shots.Append(label, shot)
500 else:
501
502 label = due_template % (
503 shot['time_left'].days / 7,
504 shot['seq_no'],
505 shot['indication'],
506 shot['regime'],
507 shot['latest_due'].strftime('%m/%Y'),
508 shot['vacc_comment']
509 )
510 self.LBOX_missing_shots.Append(label, shot)
511
512 lbl_template = _('due now: booster for %s in schedule "%s" (%s)')
513 for shot in missing_shots['boosters']:
514
515 label = lbl_template % (
516 shot['l10n_indication'],
517 shot['regime'],
518 shot['vacc_comment']
519 )
520 self.LBOX_missing_shots.Append(label, shot)
521 print "displaying missing shots took", time.time()-t1, "seconds"
522
523 return True
524
527
528
529
530
531
532
535
536
537
538
539
540
541
542
543
544 if __name__ == "__main__":
545 _log.SetAllLogLevels(gmLog.lData)
546 app = wxPyWidgetTester(size = (600, 600))
547 app.SetWidget(cImmunisationsPanel, -1)
548 app.MainLoop()
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678