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