Package Gnumed :: Package business :: Module gmVaccination
[frames] | no frames]

Source Code for Module Gnumed.business.gmVaccination

  1  """GNUmed vaccination related business objects. 
  2  """ 
  3  #============================================================ 
  4  # $Source: /cvsroot/gnumed/gnumed/gnumed/client/business/gmVaccination.py,v $ 
  5  # $Id: gmVaccination.py,v 1.38 2008/02/25 17:31:41 ncq Exp $ 
  6  __version__ = "$Revision: 1.38 $" 
  7  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
  8  __license__ = "GPL" 
  9   
 10  import types, copy, logging 
 11   
 12   
 13  from Gnumed.pycommon import gmExceptions, gmI18N, gmBusinessDBObject 
 14   
 15   
 16  _log = logging.getLogger('gm.vaccination') 
 17  _log.info(__version__) 
 18  #============================================================ 
19 -class cVaccination(gmBusinessDBObject.cBusinessDBObject):
20 """Represents one vaccination event. 21 """ 22 _cmd_fetch_payload = """ 23 select *, NULL as is_booster, -1 as seq_no, xmin_vaccination from clin.v_pat_vaccinations4indication 24 where pk_vaccination=%s 25 order by date desc""" 26 _cmds_lock_rows_for_update = [ 27 """select 1 from clin.vaccination where id=%(pk_vaccination)s and xmin=%(xmin_vaccination)s for update""" 28 ] 29 _cmds_store_payload = [ 30 """update clin.vaccination set 31 clin_when=%(date)s, 32 narrative=%(narrative)s, 33 fk_provider=%(pk_provider)s, 34 fk_vaccine=(select pk from clin.vaccine where trade_name=%(vaccine)s), 35 site=%(site)s, 36 batch_no=%(batch_no)s 37 where id=%(pk_vaccination)s""", 38 """select xmin_vaccination from clin.v_pat_vaccinations4indication where pk_vaccination=%(pk_vaccination)s""" 39 ] 40 _updatable_fields = [ 41 'date', 42 'narrative', 43 'pk_provider', 44 'vaccine', 45 'site', 46 'batch_no', 47 # the following two are updatable via __setitem__ 48 # API but not persisted via _cmds_store_payload 49 'is_booster', 50 'seq_no' 51 ] 52 #--------------------------------------------------------
53 - def _init_from_row_data(self, row=None):
54 """Make sure we have is_booster/seq_no when loading from row data.""" 55 gmBusinessDBObject.cBusinessDBObject._init_from_row_data(self, row=row) 56 try: 57 idx = self._idx['is_booster'] 58 except KeyError: 59 idx = len(self._payload) 60 self._payload.append(False) 61 # make local copy so we can safely modify it, but from 62 # self._idx which is row['idx'] with possible modifications 63 self._idx = copy.copy(self._idx) 64 self._idx['is_booster'] = idx 65 try: 66 idx = self._idx['seq_no'] 67 except KeyError: 68 idx = len(self._payload) 69 self._payload.append(False) 70 self._idx = copy.copy(self._idx) 71 self._idx['seq_no'] = -1
72 #--------------------------------------------------------
73 - def __setitem__(self, attribute, value):
74 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value) 75 if attribute in ['is_booster', 'seq_no']: 76 self._is_modified = False
77 # #-------------------------------------------------------- 78 # def get_next_shot_due(self): 79 # """ 80 # Retrieves next shot due date 81 # """ 82 # # FIXME: this will break due to not being initialized 83 # return self.__next_shot_due 84 # #-------------------------------------------------------- 85 # def set_next_shot_due(self, next_shot_due): 86 # """ 87 # Sets next shot due date 88 # 89 # * next_shot_due : Scheduled date for next vaccination shot 90 # """ 91 # self.__next_shot_due = next_shot_due 92 #============================================================
93 -class cMissingVaccination(gmBusinessDBObject.cBusinessDBObject):
94 """Represents one missing vaccination. 95 96 - can be due or overdue 97 """ 98 _cmd_fetch_payload = """ 99 (select *, False as overdue 100 from clin.v_pat_missing_vaccs vpmv 101 where 102 pk_patient=%(pat_id)s 103 and 104 (select dob from dem.identity where pk=%(pat_id)s) between (now() - age_due_min) and (now() - coalesce(age_due_max, '115 years'::interval)) 105 and 106 indication=%(indication)s 107 and 108 seq_no=%(seq_no)s 109 order by time_left) 110 111 UNION 112 113 (select *, True as overdue 114 from clin.v_pat_missing_vaccs vpmv 115 where 116 pk_patient=%(pat_id)s 117 and 118 now() - ((select dob from dem.identity where pk=%(pat_id)s)) > coalesce(age_due_max, '115 years'::interval) 119 and 120 indication=%(indication)s 121 and 122 seq_no=%(seq_no)s 123 order by amount_overdue)""" 124 _cmds_lock_rows_for_update = [] 125 _cmds_store_payload = ["""select 1"""] 126 _updatable_fields = [] 127 #--------------------------------------------------------
128 - def is_overdue(self):
129 return self['overdue']
130 #--------------------------------------------------------
131 - def create_vaccination(self):
132 # FIXME: create vaccination from myself, 133 # either pass in episode/encounter/vaccine id or use default for 134 # episode/encounter or use curr_pat.* if pk_patient=curr_pat, 135 # should we auto-destroy after create_vaccination() ? 136 return (False, 'not implemented')
137 #============================================================
138 -class cMissingBooster(gmBusinessDBObject.cBusinessDBObject):
139 """Represents one due booster. 140 """ 141 _cmd_fetch_payload = """ 142 select *, now() - amount_overdue as latest_due 143 from clin.v_pat_missing_boosters vpmb 144 where 145 pk_patient=%(pat_id)s 146 and 147 indication=%(indication)s 148 order by amount_overdue""" 149 _cmds_lock_rows_for_update = [] 150 _cmds_store_payload = ["""select 1"""] 151 _updatable_fields = []
152 #============================================================
153 -class cScheduledVaccination(gmBusinessDBObject.cBusinessDBObject):
154 """Represents one vaccination scheduled following a course. 155 """ 156 _cmd_fetch_payload = u"select * from clin.v_vaccs_scheduled4pat where pk_vacc_def=%s" 157 _cmds_lock_rows_for_update = [] 158 _cmds_store_payload = ["""select 1"""] 159 _updatable_fields = []
160 #============================================================
161 -class cVaccinationCourse(gmBusinessDBObject.cBusinessDBObject):
162 """Represents one vaccination course. 163 """ 164 _cmd_fetch_payload = """ 165 select *, xmin_vaccination_course from clin.v_vaccination_courses 166 where pk_course=%s""" 167 _cmds_lock_rows_for_update = [ 168 """select 1 from clin.vaccination_course where id=%(pk_course)s and xmin=%(xmin_vaccination_course)s for update""" 169 ] 170 _cmds_store_payload = [ 171 """update clin.vaccination_course set 172 name=%(course)s, 173 fk_recommended_by=%(pk_recommended_by)s, 174 fk_indication=(select id from clin.vacc_indication where description=%(indication)s), 175 comment=%(comment)s 176 where id=%(pk_course)s""", 177 """select xmin_vaccination_course from clin.v_vaccination_courses where pk_course=%(pk_course)s""" 178 ] 179 _updatable_fields = [ 180 'course', 181 'pk_recommended_by', 182 'indication', 183 'comment' 184 ]
185 #============================================================
186 -class VaccByRecommender:
187 _recommended_courses = None
188 #============================================================ 189 # convenience functions 190 #------------------------------------------------------------
191 -def create_vaccination(patient_id=None, episode_id=None, encounter_id=None, staff_id = None, vaccine=None):
192 # sanity check 193 # 1) any of the args being None should fail the SQL code 194 # 2) do episode/encounter belong to the patient ? 195 cmd = """ 196 select pk_patient 197 from clin.v_pat_episodes 198 where pk_episode=%s 199 union 200 select pk_patient 201 from clin.v_pat_encounters 202 where pk_encounter=%s""" 203 rows = gmPG.run_ro_query('historica', cmd, None, episode_id, encounter_id) 204 if (rows is None) or (len(rows) == 0): 205 _log.error('error checking episode [%s] <-> encounter [%s] consistency' % (episode_id, encounter_id)) 206 return (False, _('internal error, check log')) 207 if len(rows) > 1: 208 _log.error('episode [%s] and encounter [%s] belong to more than one patient !?!' % (episode_id, encounter_id)) 209 return (False, _('consistency error, check log')) 210 # insert new vaccination 211 queries = [] 212 if type(vaccine) == types.IntType: 213 cmd = """insert into clin.vaccination (fk_encounter, fk_episode, fk_patient, fk_provider, fk_vaccine) 214 values (%s, %s, %s, %s, %s)""" 215 else: 216 cmd = """insert into clin.vaccination (fk_encounter, fk_episode, fk_patient, fk_provider, fk_vaccine) 217 values (%s, %s, %s, %s, (select pk from clin.vaccine where trade_name=%s))""" 218 vaccine = str(vaccine) 219 queries.append((cmd, [encounter_id, episode_id, patient_id, staff_id, vaccine])) 220 # get PK of inserted row 221 cmd = "select currval('clin.vaccination_id_seq')" 222 queries.append((cmd, [])) 223 result, msg = gmPG.run_commit('historica', queries, True) 224 if (result is None) or (len(result) == 0): 225 return (False, msg) 226 try: 227 vacc = cVaccination(aPK_obj = result[0][0]) 228 except gmExceptions.ConstructorError: 229 _log.exception('cannot instantiate vaccination' % (result[0][0]), sys.exc_info, verbose=0) 230 return (False, _('internal error, check log')) 231 232 return (True, vacc)
233 #--------------------------------------------------------
234 -def get_vacc_courses():
235 # FIXME: use cVaccinationCourse 236 cmd = 'select name from clin.vaccination_course' 237 rows = gmPG.run_ro_query('historica', cmd) 238 if rows is None: 239 return None 240 if len(rows) == 0: 241 return [] 242 data = [] 243 for row in rows: 244 data.extend(rows) 245 return data
246 #--------------------------------------------------------
247 -def get_vacc_regimes_by_recommender_ordered(pk_patient=None, clear_cache=False):
248 # check DbC, if it fails exception is due 249 int(pk_patient) 250 251 cmd = """ 252 select fk_regime 253 from clin.lnk_pat2vacc_reg l 254 where l.fk_patient = %s""" % pk_patient 255 256 rows = gmPG.run_ro_query('historica', cmd) 257 active = [] 258 if rows and len(rows): 259 active = [ r[0] for r in rows] 260 261 # FIXME: this is patient dependant so how would a cache 262 # FIXME: work that's not taking into account pk_patient ? 263 # recommended_regimes = VaccByRecommender._recommended_regimes 264 # if not clear_cache and recommended_regimes: 265 # return recommended_regimes, active 266 267 r = ( {}, [] ) 268 269 # FIXME: use views ? 270 cmd = """ 271 select 272 r.pk_regime , 273 r.pk_recommended_by , 274 r.indication, 275 r.regime , 276 extract (epoch from d.min_age_due) /60/60/24, 277 extract (epoch from d.max_age_due) /60/60/24, 278 extract (epoch from d.min_interval ) /60/60/24, 279 d.seq_no 280 from 281 clin.v_vaccination_courses r, clin.vacc_def d 282 where 283 d.fk_regime = r.pk_regime 284 order by 285 r.pk_recommended_by, d.min_age_due""" 286 #print cmd 287 #import pdb 288 #pdb.set_trace() 289 # 290 rows = gmPG.run_ro_query('historica', cmd) 291 if rows is None: 292 VaccByRecommender._recommended_regimes = r 293 return r, active 294 295 row_fields = ['pk_regime', 'pk_recommender', 'indication' , 'regime', 'min_age_due', 'max_age_due', 'min_interval', 'seq_no' ] 296 297 for row in rows: 298 m = {} 299 for k, i in zip(row_fields, range(len(row))): 300 m[k] = row[i] 301 pk_recommender = m['pk_recommender'] 302 303 if not pk_recommender in r[0].keys(): 304 r[0][pk_recommender] = [] 305 r[1].append(pk_recommender) 306 r[0][pk_recommender].append(m) 307 308 for k, v in r[0].items(): 309 print k 310 for x in v: 311 print '\t', x 312 313 VaccByRecommender._recommended_regimes = r 314 return r, active
315 #--------------------------------------------------------
316 -def get_missing_vaccinations_ordered_min_due(pk_patient):
317 # DbC 318 int(pk_patient) 319 320 cmd = """ 321 select 322 indication, regime, 323 pk_regime, 324 pk_recommended_by, 325 seq_no , 326 extract(epoch from age_due_min) /60/60/24 as age_due_min, 327 extract(epoch from age_due_max) /60/60/24 as age_due_max, 328 extract(epoch from min_interval)/60/60/24 as min_interval 329 from 330 clin.v_pat_missing_vaccs 331 where pk_patient = %s 332 order by age_due_min, pk_recommended_by, indication 333 """ % pk_patient 334 335 rows = gmPG.run_ro_query('historica', cmd) 336 337 return rows
338 #--------------------------------------------------------
339 -def get_indications_from_vaccinations(vaccinations=None):
340 """Retrieves vaccination bundle indications list. 341 342 * vaccinations = list of any type of vaccination 343 - indicated 344 - due vacc 345 - overdue vaccs 346 - due boosters 347 - arbitrary 348 """ 349 # FIXME: can we not achieve this by: 350 # [lambda [vacc['indication'], ['l10n_indication']] for vacc in vaccination_list] 351 # I think we could, but we would be lacking error handling 352 if vaccinations is None: 353 _log.error('list of vaccinations must be supplied') 354 return (False, [['ERROR: list of vaccinations not supplied', _('ERROR: list of vaccinations not supplied')]]) 355 if len(vaccinations) == 0: 356 return (True, [['empty list of vaccinations', _('empty list of vaccinations')]]) 357 inds = [] 358 for vacc in vaccinations: 359 try: 360 inds.append([vacc['indication'], vacc['l10n_indication']]) 361 except KeyError: 362 try: 363 inds.append([vacc['indication'], vacc['indication']]) 364 except KeyError: 365 inds.append(['vacc -> ind error: %s' % str(vacc), _('vacc -> ind error: %s') % str(vacc)]) 366 return (True, inds)
367 #--------------------------------------------------------
368 -def put_patient_on_schedule(patient_id=None, course=None):
369 """ 370 Schedules a vaccination course for a patient 371 372 * patient_id = Patient's PK 373 * course = course object or Vaccination course's PK 374 """ 375 # FIXME: add method schedule_vaccination_course() to gmPerson.cPatient 376 if isinstance(course, cVaccinationCourse): 377 course_id = course['pk_course'] 378 else: 379 course_id = course 380 381 # insert new patient - vaccination course relation 382 queries = [] 383 cmd = """insert into clin.lnk_pat2vacc_reg (fk_patient, fk_course) 384 values (%s, %s)""" 385 queries.append((cmd, [patient_id, course_id])) 386 result, msg = gmPG.run_commit('historica', queries, True) 387 if result is None: 388 return (False, msg) 389 return (True, msg)
390 #--------------------------------------------------------
391 -def remove_patient_from_schedule(patient_id=None, course=None):
392 """unSchedules a vaccination course for a patient 393 394 * patient_id = Patient's PK 395 * course = course object or Vaccination course's PK 396 """ 397 # FIXME: add method schedule_vaccination_course() to gmPerson.cPatient 398 if isinstance(course, cVaccinationCourse): 399 course_id = course['pk_course'] 400 else: 401 course_id = course 402 403 # delete patient - vaccination course relation 404 queries = [] 405 cmd = """delete from clin.lnk_pat2vacc_reg where fk_patient = %s and fk_course = %s""" 406 407 queries.append((cmd, [patient_id, course_id])) 408 result, msg = gmPG.run_commit('historica', queries, True) 409 if result is None: 410 return (False, msg) 411 return (True, msg)
412 #--------------------------------------------------------
413 -def get_matching_vaccines_for_indications( all_ind):
414 415 quoted_inds = [ "'"+x + "%'" for x in all_ind] 416 417 # cmd_inds_per_vaccine = """ 418 # select count(v.trade_name) , v.trade_name 419 # from 420 # clin.vaccine v, clin.lnk_vaccine2inds l, clin.vacc_indication i 421 # where 422 # v.pk = l.fk_vaccine and l.fk_indication = i.id 423 # group 424 # by trade_name 425 # """ 426 427 cmd_inds_per_vaccine = """ 428 select 429 count(trade_name), 430 trade_name 431 from clin.v_inds4vaccine 432 group by trade_name""" 433 434 cmd_presence_in_vaccine = """ 435 select count(v.trade_name) , v.trade_name 436 437 from 438 clin.vaccine v, clin.lnk_vaccine2inds l, clin.vacc_indication i 439 where 440 v.pk = l.fk_vaccine and l.fk_indication = i.id 441 and 442 i.description like any ( array [ %s ] ) 443 group 444 445 by trade_name 446 447 """ % ', '.join( quoted_inds ) 448 449 inds_per_vaccine = gmPG.run_ro_query( 'historica', cmd_inds_per_vaccine) 450 451 presence_in_vaccine = gmPG.run_ro_query( 'historica', cmd_presence_in_vaccine) 452 453 map_vacc_count_inds = dict ( [ (x[1], x[0]) for x in inds_per_vaccine ] ) 454 455 matched_vaccines = [] 456 for (presence, vaccine) in presence_in_vaccine: 457 if presence == len ( all_ind) : 458 # matched the number of indications selected with a vaccine 459 # is this also ALL the vaccine's indications ? 460 if map_vacc_count_inds[vaccine] == presence: 461 matched_vaccines.append(vaccine) 462 return matched_vaccines
463 #============================================================ 464 # main - unit testing 465 #------------------------------------------------------------ 466 if __name__ == '__main__': 467 import sys 468 469 from Gnumed.pycommon import gmPG 470 #--------------------------------------------------------
471 - def test_vacc():
472 vacc = cVaccination(aPK_obj=1) 473 print vacc 474 fields = vacc.get_fields() 475 for field in fields: 476 print field, ':', vacc[field] 477 print "updatable:", vacc.get_updatable_fields()
478 #--------------------------------------------------------
479 - def test_due_vacc():
480 # Test for a due vaccination 481 pk_args = { 482 'pat_id': 12, 483 'indication': 'meningococcus C', 484 'seq_no': 1 485 } 486 missing_vacc = cMissingVaccination(aPK_obj=pk_args) 487 fields = missing_vacc.get_fields() 488 print "\nDue vaccination:" 489 print missing_vacc 490 for field in fields: 491 print field, ':', missing_vacc[field] 492 # Test for an overdue vaccination 493 pk_args = { 494 'pat_id': 12, 495 'indication': 'haemophilus influenzae b', 496 'seq_no': 2 497 } 498 missing_vacc = cMissingVaccination(aPK_obj=pk_args) 499 fields = missing_vacc.get_fields() 500 print "\nOverdue vaccination (?):" 501 print missing_vacc 502 for field in fields: 503 print field, ':', missing_vacc[field]
504 #--------------------------------------------------------
505 - def test_due_booster():
506 pk_args = { 507 'pat_id': 12, 508 'indication': 'tetanus' 509 } 510 missing_booster = cMissingBooster(aPK_obj=pk_args) 511 fields = missing_booster.get_fields() 512 print "\nDue booster:" 513 print missing_booster 514 for field in fields: 515 print field, ':', missing_booster[field]
516 #--------------------------------------------------------
517 - def test_scheduled_vacc():
518 scheduled_vacc = cScheduledVaccination(aPK_obj=20) 519 print "\nScheduled vaccination:" 520 print scheduled_vacc 521 fields = scheduled_vacc.get_fields() 522 for field in fields: 523 print field, ':', scheduled_vacc[field] 524 print "updatable:", scheduled_vacc.get_updatable_fields()
525 #--------------------------------------------------------
526 - def test_vaccination_course():
527 vaccination_course = cVaccinationCourse(aPK_obj=7) 528 print "\nVaccination course:" 529 print vaccination_course 530 fields = vaccination_course.get_fields() 531 for field in fields: 532 print field, ':', vaccination_course[field] 533 print "updatable:", vaccination_course.get_updatable_fields()
534 #--------------------------------------------------------
535 - def test_put_patient_on_schedule():
536 result, msg = put_patient_on_schedule(patient_id=12, course_id=1) 537 print '\nPutting patient id 12 on schedule id 1... %s (%s)' % (result, msg)
538 #-------------------------------------------------------- 539 540 test_vaccination_course() 541 #test_put_patient_on_schedule() 542 test_scheduled_vacc() 543 test_vacc() 544 test_due_vacc() 545 # test_due_booster() 546 #============================================================ 547 # $Log: gmVaccination.py,v $ 548 # Revision 1.38 2008/02/25 17:31:41 ncq 549 # - logging cleanup 550 # 551 # Revision 1.37 2008/01/30 13:34:50 ncq 552 # - switch to std lib logging 553 # 554 # Revision 1.36 2007/07/17 11:13:42 ncq 555 # - no more gmClinItem 556 # 557 # Revision 1.35 2007/03/08 11:31:08 ncq 558 # - just cleanup 559 # 560 # Revision 1.34 2007/02/22 17:27:44 ncq 561 # - no more cPerson 562 # 563 # Revision 1.33 2006/11/24 14:15:36 ncq 564 # - u'' one query 565 # 566 # Revision 1.32 2006/10/25 07:17:40 ncq 567 # - no more gmPG 568 # - no more cClinItem 569 # 570 # Revision 1.31 2006/07/19 20:25:00 ncq 571 # - gmPyCompat.py is history 572 # 573 # Revision 1.30 2006/05/06 18:53:56 ncq 574 # - select age(...) <> ...; -> select ... <> now() - ...; as per Syan 575 # 576 # Revision 1.29 2006/05/04 17:55:08 ncq 577 # - lots of DDL naming adjustments 578 # - many things spelled out at Richard's request 579 # - regime -> course 580 # 581 # Revision 1.28 2006/01/10 23:22:53 sjtan 582 # 583 # id has become pk 584 # 585 # Revision 1.27 2006/01/09 10:43:07 ncq 586 # - more dem schema qualifications 587 # 588 # Revision 1.26 2006/01/07 11:23:24 ncq 589 # - must use """ for multi-line string 590 # 591 # Revision 1.25 2006/01/06 08:32:12 ncq 592 # - some cleanup, added view to backend, may need some fixing 593 # 594 # Revision 1.24 2006/01/05 22:39:57 sjtan 595 # 596 # vaccination use case; some sql stuff may need to be added ( e.g. permissions ); sorry in a hurry 597 # 598 # Revision 1.23 2005/12/29 21:54:35 ncq 599 # - adjust to schema changes 600 # 601 # Revision 1.22 2005/11/27 12:44:57 ncq 602 # - clinical tables are in schema "clin" now 603 # 604 # Revision 1.21 2005/03/20 12:28:50 cfmoro 605 # On create_vaccination, id_patient -> pk_patient 606 # 607 # Revision 1.20 2005/02/12 13:56:49 ncq 608 # - identity.id -> identity.pk 609 # 610 # Revision 1.19 2005/01/31 10:37:26 ncq 611 # - gmPatient.py -> gmPerson.py 612 # 613 # Revision 1.18 2005/01/02 19:55:30 ncq 614 # - don't need _xmins_refetch_col_pos anymore 615 # 616 # Revision 1.17 2004/12/20 16:45:49 ncq 617 # - gmBusinessDBObject now requires refetching of XMIN after save_payload 618 # 619 # Revision 1.16 2004/11/03 22:32:34 ncq 620 # - support _cmds_lock_rows_for_update in business object base class 621 # 622 # Revision 1.15 2004/10/27 12:11:59 ncq 623 # - add is_booster/seq_no as pseudo columns to _cmd_fetch_payload so 624 # __init_from_pk() automagically creates all the right things 625 # - enhance _init_from_row_data() to construct those fields if need be 626 # - make __setitem__ aware of is_booster/seq_no being pseudo columns 627 # that do not affect _is_modified 628 # 629 # Revision 1.14 2004/10/20 21:42:28 ncq 630 # - fix faulty appending on repeated use of set_booster_status/set_seq_no() 631 # 632 # Revision 1.13 2004/10/18 11:35:42 ncq 633 # - cleanup 634 # 635 # Revision 1.12 2004/10/12 11:16:22 ncq 636 # - robustify cVaccination.set_seq_no/set_booster_status 637 # - Carlos added cVaccinationRegime/put_patient_on_schedule 638 # - some test code 639 # 640 # Revision 1.11 2004/09/28 12:28:12 ncq 641 # - cVaccination: add set_booster_status(), set_seq_no() 642 # - add cScheduledVaccination (by Carlos) 643 # - improve testing 644 # 645 # Revision 1.10 2004/08/20 13:19:52 ncq 646 # - add license 647 # 648 # Revision 1.9 2004/06/28 12:18:52 ncq 649 # - more id_* -> fk_* 650 # 651 # Revision 1.8 2004/06/26 07:33:55 ncq 652 # - id_episode -> fk/pk_episode 653 # 654 # Revision 1.7 2004/06/13 08:03:07 ncq 655 # - cleanup, better separate vaccination code from general EMR code 656 # 657 # Revision 1.6 2004/06/08 00:48:05 ncq 658 # - cleanup 659 # 660 # Revision 1.5 2004/05/14 13:17:27 ncq 661 # - less useless verbosity 662 # - cleanup 663 # 664 # Revision 1.4 2004/05/12 14:30:30 ncq 665 # - cMissingVaccination() 666 # - cMissingBooster() 667 # 668 # Revision 1.3 2004/04/24 12:59:17 ncq 669 # - all shiny and new, vastly improved vaccinations 670 # handling via clinical item objects 671 # - mainly thanks to Carlos Moro 672 # 673 # Revision 1.2 2004/04/11 12:07:54 ncq 674 # - better unit testing 675 # 676 # Revision 1.1 2004/04/11 10:16:53 ncq 677 # - first version 678 # 679